NightPxy 个人技术博客

java-线程

Posted on By NightPxy

概述

线程创建的方法

  • 继承Thread抽象类,并实现run方法
  • 实现Runnable接口,并实现run方法
  • 继承Callable接口,并实现call方法

线程的五种状态

依据其生命周期如下

  • 新建状态
    线程对象创建后,没有启动run之前就是新建状态.
    此时的线程不止没有执行,连参与线程调度执行的资格都没有
  • 可执行状态
    线程启动run之后就是可执行状态
    线程的可执行状态不代表执行.它只是表示线程参与到操作系统的线程调度中等待被调度而已.
    什么时候启动,操作系统说了算.(极端情况下,线程run之后可能永远也不会执行)
  • 执行状态
    这是线程正在开始执行的状态.此时通过操作系统调度,CPU开始真正执行线程
  • 阻塞状态
    这是获得已经获得调度执行的线程,因为某种原因放弃对CPU的使用权停止了执行
    阻塞之后必须要等到重新恢复到可执行状态才会有机会重新执行
    根据阻塞原因的不同,阻塞状态又分为三种
    等待阻塞 就是wait产生.所以重新恢复可执行的条件就是notify
    同步阻塞 因为synchronized同步锁竞争失败进入阻塞,恢复条件就是锁释放
    其它阻塞 因为当前线程sleep或对子线程join进入阻塞
  • 死亡状态
    线程执行完毕或因异常情况退出run方法.死亡状态不可恢复

线程优先级

线程可以有优先级([1,10]),高优先级的线程比低优先级的线程有更高的几率运行
但个人非常不推荐干预线程级.事实上,调整线程优先级是一个标准的反模式

  • 首先高优先级的线程不能确定一定执行.真正决定线程执行的是操作系统的线程调度器
  • 设置高优先级线程可能会干扰操作系统对整个系统的线程调度,容易引起不可预知的后果(比如大部分的线程都是默认优先级或优先级不高,此时大批高优先级线程涌入可能会造成一些系统关键线程饿死)

总之,不要假定高优先级总是比低优先级先执行,也不要有任何逻辑依赖与线程优先级.

wait & notify & notifyAll

wait&notify必须成对出现,它们都是一种持有线程锁的线程对锁的处理.这代表使用wait&notify的前提是自身必须先持有锁.也就是必须在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){
        ....
    }
}