传统的生产者消费者问题
大约 4 分钟JavaJUC并发编程
面试必问:单例模式、排序算法、生产者消费者、死锁。
大有门道。
代码
/**
* @Desc 线程之间的通信问题:生产者消费者问题 等待唤醒,通知唤醒
* 线程交替执行:A B操作同一个变量 num = 0
* A num+1
* B num-1
* @Author HeJin
* @Date 2021/3/11 12:23
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
}
}
/**
* 资源类
* 判断等待、业务、通知
*/
class Data{
private int num = 0;
public synchronized void increment() throws InterruptedException {
if (num != 0){
// 等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
// 通知其他线程 +1完毕
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (num == 0){
// 等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
// 通知其他线程 -1完毕
this.notifyAll();
}
}
执行结果:
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
Process finished with exit code 0
代码存在的问题:如果有A、B、C、D 4个线程,还安全吗?
两个加线程,两个减线程:
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "D").start();
}
}
测试发现,线程不安全。
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
C==>1
A==>2
D==>1
D==>0
A==>1
C==>2
B==>1
B==>0
C==>1
A==>2
D==>1
D==>0
A==>1
C==>2
B==>1
B==>0
C==>1
A==>2
D==>1
D==>0
A==>1
C==>2
B==>1
B==>0
C==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
D==>0
Process finished with exit code 0
虚假唤醒
原因:虚假唤醒

解决方法:将if判断改成while。
/**
* 资源类
* 判断等待、业务、通知
*/
class Data{
private int num = 0;
public synchronized void increment() throws InterruptedException {
while (num != 0){
// 等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
// 通知其他线程 +1完毕
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while (num == 0){
// 等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
// 通知其他线程 -1完毕
this.notifyAll();
}
}
测试结果:
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
A==>1
B==>0
C==>1
B==>0
A==>1
B==>0
C==>1
B==>0
A==>1
B==>0
C==>1
B==>0
A==>1
D==>0
C==>1
D==>0
A==>1
D==>0
C==>1
D==>0
A==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
D==>0
C==>1
D==>0
Process finished with exit code 0
虚假唤醒原因
class Data{
private int num = 0;
public synchronized void increment() throws InterruptedException {
if (num != 0){
// 等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
// 通知其他线程 +1完毕
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if (num == 0){
// 等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
// 通知其他线程 -1完毕
this.notifyAll();
}
}
- 线程被唤醒后,执行开始的地方是wait之后。
- 就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码。
- 使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
拿两个加法线程A、B来说,比如A先执行,执行时调用了wait方法,那它会等待,此时会释放锁,那么线程B获得锁并且也会执行wait方法,两个加线程一起等待被唤醒。此时减线程中的某一个线程执行完毕并且唤醒了这俩加线程,那么这俩加线程不会一起执行,其中A获取了锁并且加1,执行完毕之后B再执行。如果是if的话,那么A修改完num后,B不会再去判断num的值,直接会给num+1。如果是while的话,A执行完之后,B还会去判断num的值,因此就不会执行。