线程的状态
线程分为 6 种状态,可以由 Thread.State 得知,总结如下:
- NEW
- 表示线程被创建出来还没真正启动的状态,可以认为它是个 Java 内部状态
- RUNNABLE
- 表示该线程已经在 JVM 中执行,当然由于执行需要计算资源,它可能是正在运行,也可能还在等待系统分配给它 CPU 片段,在就绪队列里面排队
- BLOCKED
- 阻塞表示线程在等待 Monitor lock。比如,线程试图通过 synchronized 去获取某个锁,但是其他线程已经独占了,那么当前线程就会处于阻塞状态。
- WAITING
- 表示正在等待其他线程采取某些操作。一个常见的场景是类似生产者消费者模式,发现任务条件尚未满足,就让当前消费者线程等待(wait),另外的生产者线程去准备任务数据,然后通过类似 notify 等动作,通知消费线程可以继续工作了。Thread.join() 也会令线程进入等待状态。
- TIME_WAITING
- 其进入条件和等待状态类似,但是调用的是存在超时条件的方法,比如 wait 或 join 等方法的指定超时版本
- TERMINATED
- 不管是意外退出还是正常执行结束,线程已经完成使命,终止运行,也有人把这个状态叫作死亡
下面是这 6 种状态的例子
public class ThreadMain {public static void main(String[] args) {TaskQueue queue = new TaskQueue();final Thread task = new Thread(() -> {try {String s = queue.getTask();System.out.println("任务线程 1 拿到了任务 " + s);} catch (InterruptedException e) {e.printStackTrace();}}, "任务线程 1");final Thread task2 = new Thread(() -> {try {Thread.sleep(100);String s = queue.getTask();System.out.println("任务线程 2 拿到了任务 " + s);} catch (InterruptedException e) {e.printStackTrace();}}, "任务线程 2");print(task,1);task.start();print(task,2);task2.start();Thread putTask = new Thread(() -> {print(task,3);print(task2,4);try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}print(task2,5);queue.addTask("S 级任务");try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}print(task2,6);}, "生产线程 1");putTask.start();Thread putTask2 = new Thread(()->{queue.addTask("A 级任务");});putTask2.start();}static final Object lock = new Object();static class TaskQueue {Queue<String> queue = new LinkedList<>();public void addTask(String s) {synchronized (lock) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}queue.add(s);lock.notifyAll();}}public String getTask() throws InterruptedException {synchronized (lock) {while (queue.isEmpty()) {lock.wait();}return queue.remove();}}}static void print(Thread thread,int i) {System.out.println(i+" "+thread.getName() + " " + thread.getState());}}
得到了如下输出:
1 任务线程 1 NEW2 任务线程 1 RUNNABLE3 任务线程 1 WAITING4 任务线程 2 TIMED_WAITING5 任务线程 2 BLOCKED任务线程 1 拿到了任务 A 级任务任务线程 2 拿到了任务 S 级任务6 任务线程 2 TERMINATED
简单分下 1,2 输出代表着线程的创建和运行状态,这没啥好说的。
3 行输出是指任务线程 1 在执行 getTask 方法时执行了 wait,所以变成了 WAITING 状态。
4 行输出是由于任务线程 2 在执行睡眠 100 的操作,所以是 TIMED_WAITING 状态。
5 行输出是指 任务线程 2 要执行 getTask 方法时,发现这个锁正在被 addTask 方法持有(putTask2线程执行的),所以就是 BLOCKED 状态
剩下的输出就比较直观简单,就不分析了
上边的例子使用了 Object 提供的 wait/notifyAll 方法去更改线程的状态,如果我们持有某个对象的 Monitor 锁,调用 wait 会让线程处于 WAITING 状态,直到其他线程 notify 或者 notifyAll。
线程本身的 join 方法也会让线程处于 WAITING 状态等待线程结束。yield 是告诉调度器主动让出 CPU,下面是线程状态切换总结的一张图
wait/notify/notifyAll 看起来很简单,但是在实际应用中非常的晦涩、易错,所以在大多数情况下我们已经不再去使用了。作为替代的就是 Java 后来引入的并发包
上边的例子其实也算介绍了间接 Object 类里的 wait/notifyAll,Thread 里也有 yield 与 join 方法比较容易搞混,下面也去举个例子说明下
- yield:让出此次线程调度,此操作并不能保证其他线程会抢到执行权,有可能又被这个线程抢走了
- join:等待线程结束,调用 join 后,线程会进入到等待状态
并发包总结
这个有点多,参考下面的博客
语雀内容
