进程:系统中能独立运行并作为资源分配的基本单位,是一个独立运行的活动实体
线程:线程是进程中的一个实体,是系统调度和分派的基本单位
Java 线程的6种状态
Java 线程在运行的生命周期中可能有6种不同的状态,在某个时刻,线程只能处于其中的一个状态。
Java 线程的状态:
状态名称 | 说明 |
NEW | 初始状态,线程被构建,但是还没有调用 start()方法 |
RUNNABLE | 运行状态,Java 线程将操作系统中的就绪和运行两种状态称作“运行中” |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,当前线程需要等待其他线程做出一些特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,可以在指定的时间自动返回 |
TERMINATED | 中止状态,表示当前线程已经执行完毕 |
注意:Java 将操作系统中的运行和就绪两个状态合并称为运行状态。阻塞状态是线程阻塞在进入 synchronized 关键字修饰的方法或代码块时的状态,但是阻塞在 Lock 接口的线程状态却是等待状态。
理解中断
中断操作是一种简便的线程间交互方式,这种交互方式最适合用来取消或停止任务。除了中断之外,还可以利用一个 boolean 变量来控制是否需要停止任务并终止该线程。
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作,即其他线程通过调用该线程的 interrupt() 方法对其进行中断操作。线程通过检查自身是否被中断来进行响应,线程通过方法 isInterrupted() 判断线程是否被中断,也可以调用 Thread.interrupted() 对当前线程的中断标识位进行复位。
抛出 InterruptedException 的方法在抛出异常之前,Java 虚拟机会先将线程的中断标识位清除,然后抛出,此时调用 isInterrupted() 方法返回 false。
线程间通信
- 等待/通知机制
等待/通知的相关方法:
方法名称 | 描述 |
notify() | 通知一个在对象上等待的线程,使其从 wait()方法返回,而返回的前提是该线程获取到了对象的锁 |
notifyAll() | 通知所有等待在该对象上的线程 |
wait() | 使线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用此方法,会释放对象的锁 |
wait(long) | 超时等待一段时间,参数时间是毫秒,如果规定的时间内没有通知就会超时返回 |
wait(long,int) | 对于超时时间更细粒度的控制,可以达到纳秒 |
等待/通知是通过 Java 的 Object 超类中的 wait() 等方法控制的。它的机制是指一个线程 A 调用了对象 O 的 wait() 方法进入等待状态,而另一个线程 B 调用了对象 O 的notify() 或者 notifyAll() 方法,线程 A 收到通知后从对象 O 的 wait() 方法返回,进而执行线程 A 的后续操作。线程 A、B 通过对象 O 完成交互。
调用 wait()、notify() 和 notifyAll() 时需要注意的细节:
1)这些方法与 synchronized 同步关键字配合使用
2)使用 wait、notify 和 notifyAll 方法时需要先获取锁
3)调用 wait 方法后,线程状态由 RUNNING 变为 WAITING,并将当前线程放置到对象的等待队列
4)notify 或 notifyAll 方法被调用后,等待线程依旧不会从 wait 返回,需要等调用 notify 或 notifyAll 的线程释放锁之后,等待线程才有机会从 wait 返回
5)notify 方法将线程等待队列中的一个等待线程从等待队列中移动到同步队列中,而 notifyAll 方法是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由 WAITING 变为 BLOCKED
6)从 wait 方法返回的前提是获得了调用对象的锁
- 等待/通知的经典范式
范式分为两部分:等待方(消费者)和通知方(生产者)
消费者范式原则如下:
1)获取对象锁
2)如果条件不满足,那么调用对象的 wait() 方法,被通知后仍要检查条件
3)条件满足则执行对应的逻辑
消费者伪代码:
synchronized(对象){
while(条件不满足){
对象.wait();
}
对应的逻辑
}
生产者范式原则如下:
1)获得对象的锁
2)改变条件
3)通知所有等待在对象上的线程
生产者伪代码:
synchronized(对象){
改变条件
对象.notifyAll();
}
基于 wait 和 notifyAll 的多线程的生产者消费者 Demo
需求:
1)两个生产者线程,两个消费者线程
2)一个商品只能被消费一次
public class ProducerConsumer {
public static void main(String[] args) {
Goods goods = new Goods();
new Thread(new Producer(goods), "生产者---01---").start();
new Thread(new Producer(goods), "生产者---02---").start();
new Thread(new Consumer(goods), "---消费者---01---").start();
new Thread(new Consumer(goods), "---消费者---02---").start();
}
static class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
public void run() {
while (true) {
try {
goods.get();
} catch (InterruptedException e) {
}
}
}
}
static class Producer implements Runnable {
private Goods goods;
private Producer(Goods goods) {
this.goods = goods;
}
public void run() {
while (true) {
try {
goods.set("商品");
} catch (InterruptedException e) {
}
}
}
}
static class Goods {
private String name;
private int count = 1;
// true:生产的商品还未被消费;false:商品缺货
private boolean flag = false;
public synchronized void set(String name) throws InterruptedException {
// 多个生产者,一定要用 while 循环判断,否则会死锁
while (this.flag) {
this.wait();
}
this.name = name + count++;
this.flag = true;
System.out.println(Thread.currentThread().getName() + this.name);
// 唤醒所有在 goods 上等待线程,
// 生产者线程被唤醒,循环判断 flag 是否为 true,
// 如果有商品未被消费,当前生产则线程调用 wait()
this.notifyAll();
}
public synchronized void get() throws InterruptedException {
// 多个消费者,一定要用 while 循环判断,否则会死锁
while (!this.flag) {
this.wait();
}
this.flag = false;
System.out.println(Thread.currentThread().getName() + this.name);
// 唤醒所有在 goods 上等待线程,
// 消费者线程被唤醒,循环判断 flag 是否为 false,
// 如果没有商品,当前消费者线程调用 wait()
this.notifyAll();
}
}
}