前置知识
wait()方法:让当前线程处于等待状态,会释放当前的锁对象。其他线程可以重新拿到锁。
notifyAll()方法:唤醒所有线程。
synchronized :当前线程拿到锁后,其他现在就无法再去获取锁,会进行监视等待。
这个前置知识必须要先知道,后面也会演示给同学们看
for example:
public class Test {
public static void main(String[] args) {
Test2 test2 = new Test2();
new Thread(() -> {
try {
test2.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
test2.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
class Test2 {
private int i = 0;
public void add() throws InterruptedException {
synchronized (this) {
i++;
System.out.println(i);
//this.wait();
while (true) {
}
}
}
}
- 很明显程序暂未停止,thread-0一直处于RUNNING状态运行状态中,thread-0一直在while死循环中。
thread-1一直处于监视状态,等待这 thread-0释放锁对象。
当我们将wait()方法给加上后如图所示:
很明显可以看到threa-1也成功拿到了这个锁对象,且执行了业务代码,然后执行wait方法,然后线程的状态也处于了wait状态。
实现生产者和消费者
public class Example {
public static void main(String[] args) {
Date date = new Date();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
date.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
date.down();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
class Date {
private int num = 0;
public synchronized void add() throws InterruptedException {
if (num != 0) {
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
this.notifyAll();
}
public synchronized void down() throws InterruptedException {
if (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
this.notifyAll();
}
}
控制台输出如下所示:
这样就是简单的实现了一个生产者和消费者,thread-0生产一个消息,tread-1消费一个消息。
但是这个地方是有一个坑的。
bug: 加入我们现在有两个生产者和两个消费者就会出现问题。
public class Example {
public static void main(String[] args) {
Date date = new Date();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
date.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
date.down();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
date.add();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
date.down();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
class Date {
private int num = 0;
public synchronized void add() throws InterruptedException {
if (num != 0) {
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "=>" + num);
this.notifyAll();
}
public synchronized void down() throws InterruptedException {
if (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "=>" + num);
this.notifyAll();
}
}
现在是两个生产者和两个消费者,控制台输入如下:
- 很明显这个时候已经是错的,理论上说控制台上只应该出现1和0,因为生产一个消息,就会立马被消费掉。
什么原因导致的呢 — 虚假唤醒
- 当一个线程调用add方法执行++后会调用notifyAll唤醒其他的线程,然后自己进入等待状态
- 由于自己调用wait方法进入了等待状态就会释放当前锁对象,所以其他调用add的线程也会成功拿到锁,满足if条件后进入wait等待状态中去。
- 其他线程执行–后唤醒所有的线程,此时会唤醒add中的两个等待的线程,然后同时去进行++的操作。
如何解决
- 导致问题的关键原因就是if语句只会判断一次,当两个线程被唤醒后会立即往下执行语句。
- 所以我们需要加if语句换成while语句即可,这样当两个线程被唤醒后只有其中一个线程会拿到锁对象。
- 然后进行while判断发现已经num==0不符合条件跳出循环往下执行++,执行完后释放锁对象。
- 第二个线程再拿到锁对象,再进行判断的时候已经满足条件,继续wait等待。