线程6种状态
- New, 创建 Thread 实例之后;
- Runnable, 执行
thread.start()
之后; - Blocked, 线程试图获取 ReentrantLock 失败, 或进入 synchronize 代码块, 或调用 Block IO;
- Waiting, 调用
object.wait()
或thread.join()
之后;- 调用
object.wait()
,condition.await()
方法都会产生 WAITING 状态; - 调用
thread.join(long millis)
后, 调用者会 Waiting 一直到 thread 线程退出;
- 调用
- Time-Waitting, 调用
thread.join(long millis)
、thread.sleep(long millis)
、或者Object.wait(long)
、lock.tryLock(long)
时; - Terminated: 线程的
run()
方法正常退出或者run()
方法抛出未捕获异常时;
上面的状态来自 Oracle JDK 8
java.lang.Thread.State
, 并不等同于 unix 下的原生线程状态,
Thread.State (Java Platform SE 8 )
图-线程6种状态的转换:
线程控制 API
start
// 第一种Runnable接口 |
interrupt
- 调用
t.interrupt()
方法时, 线程t
会收到中断信号, Java 并没有要求线程一定响应中断. 线程应该根据情况决定是否响应中断, 循环调用t.isInterrupted()
可以检测线程的中断标志位. - 如果线程内调用了
sleep()
或者wait()
方法让线程进入等待状态, 当调用t.interrupt()
, 线程会抛出InterruptException
, 如果你的线程里调用了可能抛出该异常的阻塞方法, 那么就不必每次调用isInterrupt()
检测中断状态了, 在 catch 里捕获该异常即可. - 如果线程已经被中断的情况下再调用
sleep()
,sleep()
方法会清除中断状态并且抛出上述异常, 并不会进入 sleep 状态, 所以线程循环中有sleep()
的也不必用isInterrupt
检查中断状态 - 可抛出中断异常的: 线程内调用
wait()
, 或者调用thread.join()
和thread.sleep()
Thread t = new Thread(new Runnable() {
public void run() {
try {
while(!Thread.currentThread().isInterrupted() /* && */) {
Thread.sleep(5000); // 如果有sleep, 上面的isInterrupted不必要
}
} catch (InterruptedException e) {}
}
});
t.start(); // sub-thread now is "runnnable"
t.interrupt(); // main thread interrupt sub-thread
join
执行 thread.join()
的线程会进入 waiting 状态, 直到 thread
线程终止或自然退出, 继续执行后面的代码
MyThread thread = new MyThread(); |
sleep
执行 thread.sleep(m)
的线程会进入 timed_waitting 状态 m 毫秒(注意, 并没有 sleep 这种状态),Thread.sleep()
与线程调度器交互,它将当前线程设置为等待一段时间的状态。一旦等待时间结束,线程状态就会被改为可运行(runnable),并开始等待 CPU 来执行后续的任务。因此,当前线程的实际休眠时间取决于线程调度器,而线程调度器则是由操作系统来进行管理的。
sleep 和 wait 的区别?
比较 thread.sleep(long millis)
和 object.wait()
:
Thread.sleep()
方法是一个静态方法,作用在当前线程上,线程进入 timed_waiting 状态;obj.wait()
方法是一个实例方法,调用obj.wait()
的线程,会进入 waiting 状态;sleep()
方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是 cpu 对线程的监控状态依然保持者,当指定的时间到了又会自动恢复 runnable。- 线程 A 里调用
obj.wait()
方法的时候,线程 A 进入等待此对象的等待队列,放弃对象锁并进入 waiting 状态,只有针对此对象调用notify()
方法后, 线程 A 才会从对象锁的等待队列中被取出。
其他不同,主要是锁的状态:
- 调用
wait()
方法时,线程在等待的时候会释放掉它所获得的 monitor,但是调用Thread.sleep()
方法时,线程在等待的时候仍然会持有 monitor 或者锁。 - Java 中的
wait()
方法应在同步代码块中调用(已经获得了对象锁的情况下, 调用obj.wait()
会放弃锁)
总结:
- 如果你需要暂定线程一段特定的时间就使用
sleep()
方法,如果你想要实现线程间通信就使用wait()
方法。
如何终止线程?
几个问题:
- 被调用了
sleep()
的线程(timed_waiting 状态)可以被interrupt()
抛出异常吗? - 调用了
thread.join()
的线程(waiting 状态)可以被interrupt()
抛出异常吗? - 调用了
object.wait()
的线程(waiting 状态)可以被interrupt()
抛出异常吗? - 调用阻塞 IO 方法被阻塞住的线程可以被
interrupt()
抛出异常吗? - 试图抢占锁(synchronized 或 ReentrantLock)但失败的线程(blocked 状态)可以被
interrupt()
抛出异常吗?
答案: 可以, 可以, 可以, 否, 否
只有处于waiting 或 timed_waiting 状态的线程才可以抛出 InterruptException
异常被中断, block 状态的线程不可以;
1)如何终止 waiting 或 timed_waiting 状态的线程呢? 有两种方式:
- 用 volatile 的标志位控制线程的循环逻辑;
- thread.interrupt(): 中断当前线程, 线程的循环里应该 try-catch
InterruptException
并处理 thread.stop(): 不推荐
2)但是对于进入 blocked 状态的线程, 是无法被 interrupt()
中断的, 所以可能的做法是: 关闭阻塞的资源
class IOBlocked implements Runnable { |
唤醒线程
如何唤醒 sleep / wait / blocked 的线程?
- 对于因调用
object.wait()
进入 waiting 状态的线程,调用object.signal()
; - 对于因调用
t.sleep()
,t.join()
而进入 timed_waiting 状态的线程, 调用t.interrupt()
可以线程抛出InterruptedException
来达到”唤醒”的效果; - 对于因为 IO 阻塞而进入的 blocked 状态的线程, 没有办法唤醒;
被弃用的方法
Thread 类不再推荐被使用的方法: ~yield,stop,suspend,resume~
yield
yield 方法会临时暂停当前正在执行的线程,来让有同样优先级的正在等待的线程有机会执行。
如果没有正在等待的线程,或者所有正在等待的线程的优先级都比较低,那么该线程会继续运行。
执行了 yield 方法的线程什么时候会继续运行由线程调度器来决定。
yield 方法不保证当前的线程会暂停或者停止,但是可以保证当前线程在调用 yield 方法时会放弃 CPU。
yield()应该做的是让当前运行线程回到 可运行状态(Runnable),以允许具有相同优先级的其他线程获得运行机会。
因此,使用 yield()的目的是让相同优先级的线程之间能适当的轮转执行。
但是,实际中无法保证 yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
stop
该方法天生是不安全的。使用 thread.stop()停止一个线程,导致释放(解锁)所有该线程已经锁定的监视器(因沿堆栈向上传播的未检查 ERROR——
ThreadDeath
而解锁)。 // → 非受检异常
如果之前受这些监视器保护的任何对象处于不一致状态,则不一致状态的对象(受损对象)将对其他线程可见,这可能导致任意的行为。
ThreadDeath 是 java.lang.Error,不是 java.lang.Exception。不受检异常意味着我们不会有意识在代码里写 Try-Catch 去处理异常, 比如在 finally 里释放锁
上面的意思是:
线程当前可能持有一个监视器(或锁),执行 thread.stop()
将会产生一个 ThreadDeath 错误(是一种不受检 ERROR),线程向上抛出错误,导致监视器被解锁。
可能导致的问题: 以银行转账的例子, 如果在”减扣 A 余额, 增加 B 余额”的过程中, 线程被 stop, 将产生业务数据的不一致.
建议 用 interrupt 替代 stop, 在线程中循环检测 thread.isInterrupted()
或者捕获 InterruptException
然后由业务代码进行收尾处理.
ThreadDeath 和 InterruptException 的区别是:
- 前者不受检, 意味着业务代码没有机会捕获并处理, 会向上层堆栈抛出错误, 线程状态变为 “Terminated”;
- 后者是受检异常, 可以被捕获并由业务代码处理;
suspend & resume
- 当某个线程的 suspend()方法被调用时,该线程会被挂起。如果该线程占有了锁,则它不会释放锁。线程在挂起的状态下还持有锁,这导致其他线程将不能访问该资源直到目标线程恢复工作。
- 线程的
resume()方法
会恢复因suspend()
方法挂起的线程,使之重新能够获得 CPU 执行。
建议使用 object.wait
和 object.notify
方法代替 suspend
& resume
线程属性
优先级
- java 中线程优先级范围
MIN_PRIORITY
~MAX_PRIORITY
(其值1~10),NORMAL_PRIORITY
(其值=5); - 线程默认情况下继承父线程的优先级;
daemon
thread.setDaemon(true); |
当 JVM 还存在一个非守护线程, JVM 就不会退出, 当存活的线程仅剩下守护线程时, JVM 才会退出.
守护线程最典型的应用就是 GC
异常处理器
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { |