线程的状态
线程分为 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 NEW
2 任务线程 1 RUNNABLE
3 任务线程 1 WAITING
4 任务线程 2 TIMED_WAITING
5 任务线程 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 后,线程会进入到等待状态
并发包总结
这个有点多,参考下面的博客
语雀内容