Java 多线程总结
一、多线程实现方式
(1)继承Thread类,覆盖run方法
(2)实现Runnable接口,覆盖run方法,将对象传人Thread对象中
实现Runnable接口比继承Thread类所具有的优势:
1)适合多个相同的程序代码的线程去处理同一个资源
2)可以避免java中的单继承的限制
3)增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4)线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
二、线程池
(1)Executors的newCachedThreadPool 方法创建了一个可扩展的线程池。
(2)Executors的newSingleThreadExecutor 方法创建了每次执行一个任务的执行器。
(3)Executors的newScheduledThreadExecutor 方法创建一个定长线程池,支持定时及周期性任务执行。
scheduleAtFixedRate 当执行任务时间大于间隔时间,此方法不会重新开启一个新的任务进行执行,而是等待原有任务执行完成,马上开启下一个任务进行执行。此时,执行间隔时间已经被打乱。
scheduleWithFixedDelay 此方法无论任务执行时间长短,都是当第一个任务执行完成之后,延迟指定时间再开始执行第二个任务。
(4)Executors的newFixedThreadPool方法创建定长线程池
- Executors.newFixedThreadPool(int)方法创建线程池ExecutorService
- 线程池execute方法提交实现Runnable接口的对象
- 线程池submit方法提交实现Runnable或者Callable接口的对象,返回Future对象,Future对象get方法阻塞等待完成可以获取线程返回值。
- 线程池shutdown方法,只能立刻interrupt那些目前没有任务,处于等待状态从blockingQueue获取任务的异常。而不能interrupt那些在任务 执行过程中的thread,或者是任务执行过程中挂起的thread.
- 线程池shutdownNow方法,不管任务是否在执行中,一律interrupt,不去判断什么锁不锁。
(5)ThreadPoolExecutor类
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue)
corePoolSize 空闲时的线程数
maximumPoolSize 最大的线程数
keepAliveTime 超过corePoolSize的空闲线程最大活跃时间
unit 时间单位
workQueue 线程阻塞队列
三、线程的状态
(1) 新建状态(New):新创建了一个线程对象。
(2) 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
(3) 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
(4) 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
3、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
(5) 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
四、Thread类
(1)Thread静态方法
- currentThread() 可以返回代码段正在被哪个线程调用的信息。
- sleep() 相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
- yield() 会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
(2)Thread成员方法
- start() 用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
- run() 不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
- getId() 的作用是取得线程的唯一标识
- isAlive() 判断当前线程是否处于活动状态,活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
- join() 方法在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
- getName和setName 用来得到或者设置线程名称。
- getPriority和setPriority 用来获取和设置线程优先级。
- setDaemon和isDaemon 用来设置线程是否成为守护线程和判断线程是否是守护线程。thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。
- interrupt() 暂停线程
- Java中有以下3种方法可以终止正在运行的线程:
- 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
- 使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
- 使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。