线程是操作系统中能够进行运算调度的最小单位了。它被包含在进程中,是进程在运行过程中,真正的『打工人』。每一个进程有且至少有一个线程存在,默认的线程我们可以把它叫作『主线程』。
在 Java 中,进行线程的切换是一件比较浪费资料的事情,这意味着代码的执行需要从用户态切换到内核态,然后再从内核态切换回用户态。但这也不是说我们不能用线程,合理的使用可以提高程序执行的吞吐率。
既然上面提到了『线程切换』,那我们就来聊聊,线程的几种状态。
线程的六种状态
根据官方文档来看,Java 中线程有六种状态,分别是 NEW
、RUNNABLE
、BLOCKED
、WAITING
、TIMED_WAITING
和 TERMINATED
。
NEW
刚创建线程对象时,线程就处在这个状态。该状态只能向下一个状态 RUNNABLE
发生转换,一旦发生转换,则无法再回到 NEW
状态。
比如下方代码:
Runnable runnable = new Runnable() {
@Override
public void run() { }
};
Thread thread = new Thread(runnable);
System.out.println("t1 state: " + thread.getState());
则会看到输出:
t1 state: NEW
RUNNABLE
当应用程序调用了线程对象的 start()
方法之后,该线程就会进入 RUNNABLE
状态。此时,该线程会被置于可运行的线程池中,等待被线程调度选中,获取 CPU 的使用权。一旦线程获取到 CPU 的使用权,则开始真正执行程序代码。我们还可以将这个状态细分为 READY
和 RUNNING
两种状态,用于区分线程是否抢占到 CPU,已经开始运行代码了。
Runnable runnable = new Runnable() {
@Override
public void run() { }
};
Thread thread = new Thread(runnable);
thread.start();
System.out.println("t1 state: " + thread.getState());
则会输出:
t1 state: RUNNABLE
BLOCKED
在这种状态下,线程一般是在等待 monitor 的锁。一般我们管这种状态叫『阻塞状态』。进入阻塞状态是因为某种原因下,线程放弃了 CPU 的使用权,线程暂停执行代码,进入阻塞状态。
用下面的例子来说明:
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new DemoThreadB());
Thread t2 = new Thread(new DemoThreadB());
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("t2 state: " + t2.getState());
System.exit(0);
}
static class DemoThreadB implements Runnable {
@Override
public void run() {
process();
}
public static synchronized void process() {
while(true) {
// 模拟重量级的过程处理,t1 如果正在运行该代码块,则 t2 就会进入 BLOCKED 状态
}
}
}
}
这个例子中创建了 t1 和 t2 两个线程。t1 在调用 start()
方法后进入 process()
方法。因为这里使用了 while(true)
来保证线程不会轻易狗带,所以 t2 就甭想抢占到 CPU 并执行 process()
方法。但 t2 也调用了 start()
方法进入了 RUNNABLE
状态,又由于比 t1 晚了一步,所以只能进入 BLOCKED
状态,等待 t1 释放锁。
该例子会输出:
t2 state: BLOCKED
WAITING
在这种状态下,线程就是在无限期等待其他线程完成某个操作。比如说,某个线程在一个对象上调用了 Object.wait()
,就是在等待另一个线程对该对象调用 Object.notifiy()
或者 Object.nofityAll()
。有可能引起线程进入 WATING
状态的方法有下面几种:
- Object.wait()
无超时的等待
- Thread.join()
无超时的等待
- LockSupport.park()
我们使用下面的例子来模拟这个状态:
public class Main {
static Thread t1;
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(new DemoThreadB());
t1 = new Thread(new DemoThreadA(t2));
t1.start();
}
static class DemoThreadA implements Runnable {
Thread t2;
DemoThreadA(Thread thread) {
this.t2 = thread;
}
@Override
public void run() {
this.t2.start();
try {
this.t2.join();
System.out.println("t1 state: " + t1.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class DemoThreadB implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 state: " + t1.getState());
}
}
}
上面的例子中,创建了两个线程 t1 和 t2,并且在 t1 的 run()
方法中启动了 t2,并且调用了 t2 的 join()
方法。此时,t1 就进入了 WAITING
状态,当 t2 的 run()
方法执行完毕后,t1 就会从 WAIT
状态回到 RUNNABLE
状态。
所以上述代码的执行结果如下:
t1 state: WAITING
t1 state: RUNNABLE
TIMED_WAITING
顾名思义,这种状态也是 WAITNG
状态,只不过这种状态是有等待时限的。有可能引起线程进入 TIMED_WAITING
状态的方法有下面几种:
- Thread.sleep(long millis)
不必介绍
- Object.wait(long timeout)
带有时限的 Object.wait()
- Thread.join(long millis)
带有时限的 Thread.join()
- LockSupport.parkNanos(Object blocker, long nanos)
- LockSupport.parkUntil(Object blocker, long deadline)
同样地,我们使用一个例子来演示这个状态:
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new DemoThreadC());
t1.start();
Thread.sleep(1000);
System.out.println("t1 state: " + t1.getState());
}
static class DemoThreadC implements Runnable {
@Override
public void run() {
try {
Thread.sleep(3000);
// Thread.join(3000);
// LockSupport.parkNanos(this, 3000000000);
// LockSupport.parkUntil(this, 3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
主线程 sleep 1 秒就结束了,而子线程 t1 则由于『业务量巨大』导致 sleep 3 秒后才能退出,在 1 秒之后观察 t1 的状态,则输出如下:
t1 state: TIMED_WAITING
TERMINATED
线程的 run()
方法执行完毕,或者因为异常导致退出了 run()
方法,则该线程的生命周期就结束了,进入 TERMINATED
状态。进入 TERMINATED
状态的线程不可再次重生。
这个例子就简单多了:
Runnable runnable = new Runnable() {
@Override
public void run() { }
};
Thread t1 = new Thread(runnable);
t1.start();
Thread.sleep(1000);
System.out.println("t1 state: " + t1.getState());
线程瞬间运行完毕(因为 run() 方法为空),1秒后观察 t1 的状态,就会有如下输出:
t1 state: TERMINATED
下面用一张图来展示这六种状态之间的关系:
按时间顺序来解释一下:
NEW
状态下的线程,在调用start()
方法之后,进入RUNNABLE
状态。此时可以细分为READY
和RUNNING
两种状态,READY
状态下还未抢占到 CPU,代码无法执行;抢占到 CPU,则进入RUNNING
状态,开始执行代码;- 如果在
RUNNABLE
状态下调用了Object.wait()
或者Thread.join()
或者LockSupport.park()
,则进入WAIT
状态; - 如果在
RUNNABLE
状态下调用了Thread.sleep(long millis)
或者Object.wait(long timeout)
或者Thread.join(long millis)
或者LockSupport.parkNanos(Object locker, long nanos)
或者LockSupport.parkUntil(Object blocker, long deadline)
,则进入TIMED_WAITING
状态; - 如果在
RUNNABLE
状态下还需要等待 monitor 锁进入同步代码块或者重入同步代码块,则进入BLOCKED
状态; - 在
WAITING
或者TIMED_WAITING
状态下,如果其他线程调用了Object.notify()
或者Object.nofityAll()
方法,则进入RUNNABLE
状态, - 在
BLOCKED
状态下,一旦获取 monitor 锁,则回到RUNNABLE
状态; - 在
WAITING
或者TIMED_WAITING
或者BLOCKED
状态下,线程代码执行结束,或因为异常提前结束,都会进入TERMINATED
状态。