1、并发与并行
并行:两个或多个事件在同一时刻发生
并发:两个或多个事件在同一时间段内发生
2、进程与线程
打开浏览器中的百度、淘宝,那么浏览器是一个进程,淘宝和百度是两个线程。
3、线程创建的方式
(1)继承Thread类:
自定义线程:
public class MyThread extends Thread { public void run(){ for(int i=0;i<10;i++){ System.out.println("thread:"+new Date().getTime()); } } }
创建测试类:
public static void main(String args []){
MyThread myThread=new MyThread();
myThread.start();
for(int i=0;i<10;i++){
System.out.println("main:"+new Date().getTime());
}
}
main方法也是一个线程被称作主线程,手动创建的线程称为子线程,两个线程中分别书写for循环。
main:1585656576673 main:1585656576674 main:1585656576674 main:1585656576675 main:1585656576675 main:1585656576675 main:1585656576675 main:1585656576675 main:1585656576675 main:1585656576675 thread:1585656576675 thread:1585656576676 thread:1585656576676 thread:1585656576677 thread:1585656576678 thread:1585656576678 thread:1585656576678 thread:1585656576678 thread:1585656576678 thread:1585656576679
继承Thread类,重写run方法,创建线程的对象并调用start开启线程(注意:调用的是start方法,不是run方法)。这里循环的次数较少是先执行main线程,再去执行另外一个线程,当循环的次数较多时会出现两个线程的交叉执行的现象。
这里只开辟了一条线程,可以开辟多条线程,主线程和子线程是交替执行的
(2)实现Runnable接口:
实现接口,创建线程:
public class MyRunnable implements Runnable{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println("myRunnable:"+new Date().getTime()); } } }
测试类:
public class Test { public static void main(String args []){ Thread thread=new Thread(new MyRunnable()); thread.start(); for(int i=0;i<10;i++){ System.out.println("main:"+new Date().getTime()); } } }
main:1585657253215 main:1585657253215 main:1585657253215 main:1585657253216 main:1585657253216 main:1585657253216 main:1585657253216 main:1585657253216 main:1585657253216 main:1585657253216 myRunnable:1585657253217 myRunnable:1585657253217 myRunnable:1585657253217 myRunnable:1585657253218 myRunnable:1585657253218 myRunnable:1585657253218 myRunnable:1585657253218 myRunnable:1585657253219 myRunnable:1585657253219 myRunnable:1585657253219
需要创建线程对象,通过线程对象来开启我们的线程,采用的是代理的方式
(3)实现Callable接口
Callable接口:
Callable需要使用FutureTask类帮助执行,FutureTask类结构如下:
RunnableFuture接口需要实现Future和Runnable两个接口。
Future接口的方法:
判断任务是否完成:isDone()
能够中断任务:cancel()
能够获取任务的执行结果:get()
创建线程:
public class MyCallable implements Callable<String> { @Override public String call() throws Exception { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"执行时间"+new Date().getTime()); } return "MyCallable接口执行完成!!"; } }
在创建线程的时候设置了返回值,可以通过get方法获取。
主线程:
public class Test { public static void main(String args[]){ FutureTask<String> futureTask =new FutureTask<String>(new MyCallable()); Thread thread=new Thread(futureTask,"Mycallable"); thread.start(); for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"执行时间"+new Date().getTime()); } String result= null; try { result = futureTask.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println(result); } }
在主线程中可以获取到手动创建的线程的返回值。
4、创建线程池
分类:
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需求可灵活回收空闲线程,若无可回收,则新建线程 newFixedThreadPool:创建一个定长线程池,可控制线程的最大并发数,超出的线程会在线程池中等待。 newScheduleThreadPool: 创建一个定长线程池,支持定时及周期性任务处理 newSingleThreadScheduledExecutor:创建一个单线程化的线程池,他只用唯一的工作栈来执行任务
使用:
class Mythread implements Runnable { public void run(){ System.out.println(Thread.currentThread().getName()); } public static void main(String args []){ ExecutorService executorService = Executors.newFixedThreadPool(3);// // Executors:线程池创建工厂类,调用方法返回线程池对象 executorService.submit(new Mythread()); executorService.submit(new Mythread()); executorService.submit(new Mythread()); } }
pool-1-thread-1 pool-1-thread-2 pool-1-thread-3
好处:
限制线程的个数,不会导致由于线程过多导致系统运行缓慢,甚至崩溃
节省了资源:我们用创建的线程,在使用后都会被销毁,频繁地创建和销毁会造成时间和资源的浪费。线程池是一个能够容纳多个线程的容器,里面的线程可以反复使用。
5、实现接口和继承Thread类比较
(1)接口更适合多个相同的程序代码的线程去共享同一个资源。
public class MyRunnable implements Runnable{ @Override public void run() { for(int i=0;i<10;i++){ System.out.println("myRunnable:"+new Date().getTime()); } } }
可以new多个Thread去调用创建的线程中的run方法,代码的复用增强:
Thread thread=new Thread(new MyRunnable());
(2)接口可以避免Java中的单继承的局限性
(3)接口代码可以被多个线程共享,代码和线程独立
(4)线程池只能放入实现Runable 或callable 接口的线程,不能直接放入继承Thread的类
扩充:
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。
6、Runnable和Callable接口比较
相同点:
(1)两者都是接口
(2)两者都可用来编写多线程程序
(3)两者都需要调用Thread.start 启动续程
不同点:
(1)实现Callable接口的线程能返回执行结果,而实现Runnable 接口的线程不能返回结果
(2)Callable 接口的call()方法允许抛出异常,而 Runnable 接口的run()方法的不允许批异常
(3)实现Callable 接口的线程可以调用Future.cancel取消执行,而实现runnable 接口的线程不能,不需要控制线程的话没必要用Callable接口
注意点:
Callable 接口支持返回执行结果,此时需要调用FutureTask. get()方法(主线程会被阻塞,不能和main并发执行)实现,此方法会阻塞主线程直到获取“将来’结果;当不调用此方法时,主线程不会阻塞!
7、线程的生命周期
(1)新建
new关键字创建一个线程之后,该线程处于新建状态。jvm为线程分配内存,初始化成员变量值
(2)就绪
当线程对象调用了start()方法后,该线程处于就绪状态。jvm为线程创建方法栈和程序计数器,等待线程调度器调度
(3)运行
就绪状态的线程获得CPU资源,开始运行run方法,该线程处于运行状态
(4)阻塞
线程调用sleep方法主动放弃所占用的处理器资源
线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
线程试图获得一个同步锁(同步监视器),但是该锁正在被其它线程持有
线程正在等待某个通知
程序调用了线程的suspend()方法将该线程挂起,但是这个方法容易导致死锁,所以应该尽量避免使用该方法
(5)死亡
线程会以如下的三种方式结束,结束后就进入死亡状态:·
run()或call()方法执行完成,线程正常结束
线程抛出一个未捕获的Exception或Error
调用该线程的stop方法来结束该线程,该方法容易导致死锁,不推荐使用
进程的三种基本状态:
就绪:进程已经获得了除CPU以外的所有必要资源,只要获得了CPU便可立即执行。
执行:进程已经获得了CPU,其进程正在执行的状态。
阻塞:正在执行的进程由于IO请求、申请缓冲区失败等(如:访问临界资源)暂时无法继续执行的状态。
8、线程安全
如果多个线程同时运行同一个实现了Runnable接口的类,程序每次运行的结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的,反之,则是线程不安全的。