概述
线程创建的方法
- 继承Thread抽象类,并实现run方法
- 实现Runnable接口,并实现run方法
- 继承Callable接口,并实现call方法
线程的五种状态
依据其生命周期如下
- 新建状态
线程对象创建后,没有启动run之前就是新建状态.
此时的线程不止没有执行,连参与线程调度执行的资格都没有 - 可执行状态
线程启动run之后就是可执行状态
线程的可执行状态不代表执行.它只是表示线程参与到操作系统的线程调度中等待被调度而已.
什么时候启动,操作系统说了算.(极端情况下,线程run之后可能永远也不会执行) - 执行状态
这是线程正在开始执行的状态.此时通过操作系统调度,CPU开始真正执行线程 - 阻塞状态
这是获得已经获得调度执行的线程,因为某种原因放弃对CPU的使用权停止了执行
阻塞之后必须要等到重新恢复到可执行状态才会有机会重新执行
根据阻塞原因的不同,阻塞状态又分为三种
等待阻塞 就是wait产生.所以重新恢复可执行的条件就是notify
同步阻塞 因为synchronized同步锁竞争失败进入阻塞,恢复条件就是锁释放
其它阻塞 因为当前线程sleep或对子线程join进入阻塞 - 死亡状态
线程执行完毕或因异常情况退出run方法.死亡状态不可恢复
线程优先级
线程可以有优先级([1,10]),高优先级的线程比低优先级的线程有更高的几率运行
但个人非常不推荐干预线程级.事实上,调整线程优先级是一个标准的反模式
- 首先高优先级的线程不能确定一定执行.真正决定线程执行的是操作系统的线程调度器
- 设置高优先级线程可能会干扰操作系统对整个系统的线程调度,容易引起不可预知的后果(比如大部分的线程都是默认优先级或优先级不高,此时大批高优先级线程涌入可能会造成一些系统关键线程饿死)
总之,不要假定高优先级总是比低优先级先执行,也不要有任何逻辑依赖与线程优先级.
wait & notify & notifyAll
wait¬ify必须成对出现,它们都是一种持有线程锁的线程对锁的处理.这代表使用wait¬ify的前提是自身必须先持有锁.也就是必须在synchronized中并最终得以执行到synchronized之内才能使用.如果在没有线程控制权的代码中执行三者,会抛出java.lang.IllegalMonitorStateException异常
wait 是持有线程锁的线程释放锁,随后自身处于等待状态
notify 是自身已释放锁,通知其它竞争者可以退出wait参与竞争了
notifyAll 类似notify.区别在于notifyAll始终通知全部等待者然后大家一起竞争.而notify是始终保持不惊动其它竞争者的情况下通知其中一个(具体哪一个程序未知),也就是不会共同竞争
sleep & wait & yield & join
sleep 休眠.意思是在指定时间让线程阻塞直到一定时间后恢复执行.
需要注意的是休眠是带着锁一起休眠,也就是sleep是不释放锁的.这也是sleep让人诟病的地方
wait 释放当前锁. wait必须包括在synchronized,而sleep可以不包括在synchronized之中
yield 暂停当前线程重新回到可执行状态.它与wait一样会释放当前锁,与wait不同的是释放之后本身会再次参与竞争.也就是说yield有立即重新执行的机会,而wait释放后不会参与本次竞争
join join反映的是子线程概念.当前线程阻塞直到子线程完成执行.(比如常见的异步转同步)
线程中断
java线程中断机制
java的线程中断是一种协作机制.也就是说通过中断不能直接终止另一个线程
java采取的做法是为中断目标线程打上一个标记,然后由另一个线程自行处理.(所以理论上说如果中断目标线程不配合是中断不了的)
这个标记就是Thread.isInterrupt.目标线程可以通过检测这个标志位来感知是否有外部要求线程中断,并进一步决定如何处理
但仅有这个标志位是不够,原因在于阻塞.而能读取这个标志位的前提是线程至少是在运行状态的(非运行状态线程代码根本就没执行
对于阻塞状态,java的另一个机制是打入InterruptException异常.也就是如果线程处于阻塞状态,会以抛出InterruptException的方式告知线程
所以优雅的配合线程中断,目标线程需要对这两者同时进行处理
- 检测标志位(推荐Thread.currentThread().isInterrupted())
- 显式捕获InterruptException
线程中断的JDK有一个著名的槽点
检测中断标志位有两种方法
- 静态方法Thread.interrupted
- 实例方法 Thread.currentThread().isInterrupted()
这两种检测方法有个非常大区别就是 静态方法会自动清除标志位
也就是如果使用静态方法检测连续调用两次会返回false,这一点务必注意
自定义运行变量
还有一种常见的中断方法是自定义运行变量
//需要注意的是使用个自定义运行变量必须使用volatile修饰,强制从主内存中读取
private volatile boolean isRunning = true
public void run(){
while(true == isRunning){
....
}
}