摘录自 极客时间

Java生命周期如下:

p1

BLOCKEDWAITINGTIMED_WAITING可以理解为线程导致休眠状态的三种原因。

RUNNABLE 与 BLOCKED 的状态装换

在等待synchronized隐式锁的时候,等待的线程会从RUNNABLE转换到BLOCKED,得到了就会转回去。
那么线程调用阻塞式API的时候,Java不会转换到BLOCKED状态,因为在操作系统层面,线程会转换到休眠,但是在JVM层面,Java线程依然保持RUNNABLE状态,因为在JVM看来,等待CPU使用权(操作系统层面是可执行状态)和等待I/O(操作系统层面是休眠状态)没有区别。所以都归入了RUNNABLE状态。

RUNNABLE 与 WAITING 的状态装换

  • 获得synchronized隐式锁的线程,调用无参数的Object.wait()
  • 调用无参数的Thread.joi()方法。join()是一种线程同步的方法,调用A.join()的时候,线程会进入WAITING,等thread A执行完就会回到RUNNABLE
  • 调用LockSupport.park()。Java并发包中的锁都是基于LockSupport实现的。调用LockSupport.park(),当前线程会阻塞,调用LockSupport.unpark(Thread thread)可以唤醒目标线程,会回到RUNNABLE

RUNNABLE 与 TIMED_WAITING 的状态转换

  • 调用带参数的Thread.sleep(long millis) 方法
  • 获得synchronized隐式锁的线程,调用带超时参数的 Object.wait(long timeout) 方法
  • 调用带超时参数的Thread.join(long millis) 方法
  • 调用待超时参数的的 LockSupport.parkNanos(Object blocker, long deadline) 方法
  • 调用带超时参数的的 LockSupport.parkUntil(long deadline) 方法。

所以TIMED_WAITINGWAITING的区别就是有没有超时参数

NEW 到 RUNNABLE 状态

Java刚创建出来的Thread对象就是NEW的状态,而创建Thread对象主要有两种方法。一种是继承Thread对象,重写run()方法。

1
2
3
4
5
6
7
8
9
// 自定义线程对象
class MyThread extends Thread {
public void run() {
// 线程需要执行的代码
......
}
}
// 创建线程对象
MyThread myThread = new MyThread();

另一种是实现RUNNALE接口,重写run()方法,将该实现类作为创建Thread对象的参数

1
2
3
4
5
6
7
8
9
10
// 实现 Runnable 接口
class Runner implements Runnable {
@Override
public void run() {
// 线程需要执行的代码
......
}
}
// 创建线程对象
Thread thread = new Thread(new Runner());

NEW状态的线程,不会被OS调度,Java线程要执行,就要start()进入RUNNABLE状态。

1
2
3
MyThread myThread = new MyThread();
// 从 NEW 状态转换到 RUNNABLE 状态
myThread.start();

RUNNABLE 到TERMINATED状态

线程执行完run()后,会自动转换到TERMINATED状态,强制终止run()的执行的正确姿势是调用initerrupt()方法。
initerrupt()方法会通知线程,线程有机会执行后续的操作,也可以无视这个通知。

  • 当线程A处于WAITINGTIMED_WAITING状态(是调用了类似wait()join()sleep())时,其他线程调用线程A的initerrupt()方法会让A返回到RUNNABLE状态。
  • 当线程 A 处于 RUNNABLE 状态时,并且阻塞在 java.nio.channels.InterruptibleChannel 上时,如果其他线程调用线程 A 的 interrupt() 方法,线程 A 会触发 java.nio.channels.ClosedByInterruptException 这个异常;而阻塞在 java.nio.channels.Selector 上时,如果其他线程调用线程 A 的 interrupt() 方法,线程 A 的 java.nio.channels.Selector 会立即返回。

PS:线程A可以通过isInterrupted()方法检测自己是不是被中断了。

思考小题

下面代码的本意是当前线程被中断之后,退出while(true),那么代码是否正确

Thread th = Thread.currentThread();
while(true) {
  if(th.isInterrupted()) {
    break;
  }
  // 省略业务代码无数
  try {
    Thread.sleep(100);
  }catch (InterruptedException e){
    e.printStackTrace();
  }
}

错误,如果代码正好在sleep,那么会接受到异常后,中断标志会自动清除,然后上面的if条件永远不会被执行

Thread th = Thread.currentThread();
while(true) {
  if(th.isInterrupted()) {
    break;
  }
  // 省略业务代码无数
  try {
    Thread.sleep(100);
  }catch (InterruptedException e){
    th.interrupt();
  }
}