JDK 的 Thread 源码定义了6个状态: java.lang.Thread.State
- New
尚未启动的线程的线程状态。 - Runnable
可运行线程的线程状态,等待CPU调度。 - Blocked
线程阻塞等待监视器锁定的线程状态。处于synchronized同步代码块或方法中被阻塞。 - Waiting
等待线程的线程状态。下列不带超时的方式:Object.wait
、Thread.join
、LockSupport.park
- Timed Waiting具有指定等待时间的等待线程的线程状态。下列带超时的方式:Thread.sleep、0bject.wait、 Thread.join、 LockSupport.parkNanos、 LockSupport.parkUntil、
- Terminated
终止线程的线程状态。线程正常完成执行或出现异常。
文字说的还不是太清楚了,让我来你画个图就一目了然了:
- Thread状态机
- 上面那个图太复杂了看不懂?没问题,看个小学生版:
1 NEW
线程还没有开始执行
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了 NEW 状态。
当调用线程的start()方法,线程也不一定会马上执行,因为Java线程是映射到os的线程执行的
,此时可能还需要等os调度,但此时该线程的状态已经为RUNNABLE。
2 RUNNABLE
只是说你有资格运行,调度程序没有挑选到你,你就永远是可运行状态。
2.1条件
- 调用start()
- Thread.sleep(long millis)
一定是当前线程调用此方法,当前线程进入阻塞,不释放对象锁,millis后线程自动苏醒进入可运行态。
作用:给其它线程执行机会的最佳方式。
- 其他线程join()结束
当前线程里调用其它线程1的join方法,当前线程阻塞,但不释放对象锁,直到线程1执行完毕或者millis时间到,当前线程进入可运行状态。
- 等待用户输入完毕
- 某个线程拿到对象锁
- 当前线程时间片用完
- Thread.yield()
调用当前线程的yield()
一定是当前线程调用此方法,当前线程放弃获取的cpu时间片,由运行状态变会可运行状态,让os再次选择线程。
作用:让同优先级的线程轮流执行,但并不保证一定会轮流执行。实际无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
- 锁池里的线程拿到对象锁后,进入可运行状态
- 正在执行的线程
该状态最有争议,注释说它表示线程在JVM层面是执行的,但在os不一定,它举例是CPU,毫无疑问CPU是一个os资源,但这也就意味着在等os其它资源时,线程也会是这个状态。
I/O阻塞算是等os的资源?
3 BLOCKED
- 线程由于等待监视器锁,被阻塞。 处于阻塞态的线程在调用
Object.wait
之后正在等待监视器锁 进入 同步的块/方法或 再进入 同步的块/方法
被挂起,线程因为某原因放弃cpu 时间片,暂时停止运行。
3.1条件
- 当前线程调用
Thread.sleep()
- 运行在当前线程里的其它线程调用
join()
,当前线程进入阻塞态 - 等待用户输入时,当前线程进入阻塞态
3.2 分类
- 等待阻塞
运行的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue) - 同步阻塞
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)
- 其他阻塞
运行的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
线程在阻塞等待monitor lock(监视器锁)
一个线程在进入synchronized修饰的临界区的时候,或者在synchronized临界区中调用Object.wait然后被唤醒重新进入synchronized临界区都对应该态。
结合上面RUNNABLE的分析,也就是I/O阻塞不会进入BLOCKED状态,只有synchronized会导致线程进入该状态
关于BLOCKED状态,注释里只提到一种情况就是进入synchronized声明的临界区时会导致,这好理解,synchronized是JVM自己控制的,所以这个阻塞事件它自己能够知道(对比理解上面的os层面)。
interrupt()是无法唤醒的,只是做个标记。