线程是操作系统中能够进行运算调度的最小单位了。它被包含在进程中,是进程在运行过程中,真正的『打工人』。每一个进程有且至少有一个线程存在,默认的线程我们可以把它叫作『主线程』。
在 Java 中,进行线程的切换是一件比较浪费资料的事情,这意味着代码的执行需要从用户态切换到内核态,然后再从内核态切换回用户态。但这也不是说我们不能用线程,合理的使用可以提高程序执行的吞吐率。
既然上面提到了『线程切换』,那我们就来聊聊,线程的几种状态。
线程的六种状态
根据 官方文档 来看,Java 中线程有六种状态,分别是 NEW
、RUNNABLE
、BLOCKED
、WAITING
、TIMED_WAITING
和 TERMINATED
。
NEW
刚创建线程对象时,线程就处在这个状态。该状态只能向下一个状态 RUNNABLE
发生转换,一旦发生转换,则无法再回到 NEW
状态。
比如下方代码:
|
则会看到输出:
|
RUNNABLE
当应用程序调用了线程对象的 start()
方法之后,该线程就会进入 RUNNABLE
状态。此时,该线程会被置于可运行的线程池中,等待被线程调度选中,获取 CPU 的使用权。一旦线程获取到 CPU 的使用权,则开始真正执行程序代码。我们还可以将这个状态细分为 READY
和 RUNNING
两种状态,用于区分线程是否抢占到 CPU,已经开始运行代码了。
|
则会输出:
|
BLOCKED
在这种状态下,线程一般是在等待 monitor 的锁。一般我们管这种状态叫『阻塞状态』。进入阻塞状态是因为某种原因下,线程放弃了 CPU 的使用权,线程暂停执行代码,进入阻塞状态。
用下面的例子来说明:
|
这个例子中创建了 t1 和 t2 两个线程。t1 在调用 start()
方法后进入 process()
方法。因为这里使用了 while(true)
来保证线程不会轻易狗带,所以 t2 就甭想抢占到 CPU 并执行 process()
方法。但 t2 也调用了 start()
方法进入了 RUNNABLE
状态,又由于比 t1 晚了一步,所以只能进入 BLOCKED
状态,等待 t1 释放锁。
该例子会输出:
|
WAITING
在这种状态下,线程就是在 无限期 等待其他线程完成某个操作。比如说,某个线程在一个对象上调用了 Object.wait()
,就是在等待另一个线程对该对象调用 Object.notifiy()
或者 Object.nofityAll()
。有可能引起线程进入 WATING
状态的方法有下面几种:
- Object.wait()
无超时的等待
- Thread.join()
无超时的等待
- LockSupport.park()
我们使用下面的例子来模拟这个状态:
|
上面的例子中,创建了两个线程 t1 和 t2,并且在 t1 的 run()
方法中启动了 t2,并且调用了 t2 的 join()
方法。此时,t1 就进入了 WAITING
状态,当 t2 的 run()
方法执行完毕后,t1 就会从 WAIT
状态回到 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)
同样地,我们使用一个例子来演示这个状态:
|
主线程 sleep 1 秒就结束了,而子线程 t1 则由于『业务量巨大』导致 sleep 3 秒后才能退出,在 1 秒之后观察 t1 的状态,则输出如下:
|
TERMINATED
线程的 run()
方法执行完毕,或者因为异常导致退出了 run()
方法,则该线程的生命周期就结束了,进入 TERMINATED
状态。进入 TERMINATED
状态的线程不可再次重生。
这个例子就简单多了:
|
线程瞬间运行完毕(因为 run() 方法为空),1 秒后观察 t1 的状态,就会有如下输出:
|
下面用一张图来展示这六种状态之间的关系:
按时间顺序来解释一下:
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
状态。