在面试中,生产者与消费者是高频问题之一
1.生产者和消费者问题 Synchronized 版
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();
}
}
// 判断等待,业务,通知
class Data {
// 数字 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
/*
假设 number此时等于1,即已经被生产了产品
如果这里用的是if判断,如果此时A,C两个生产者线程争夺increment()方法执行权
假设A拿到执行权,经过判断number!=0成立,则A.wait()开始等待(wait()会释放锁),然后C试图去执行
生产方法,但依然判断number!=0成立,则B.wait()开始等待(wait()会释放锁)
碰巧这时候消费者线程线程B/D去消费了一个产品,使number=0然后,B/D消费完后调用this.notifyAll();
这时候2个等待中的生产者线程继续生产产品,而此时number++ 执行了2次
同理,重复上述过程,生产者线程继续wait()等待,消费者调用this.notifyAll();
然后生产者继续超前生产,最终导致‘产能过剩’,即number大于1
if(number != 0){
// 等待
this.wait();
}*/
while (number != 0) {
// 注意这里不可以用if 否则会出现虚假唤醒问题,解决方法将if换成while
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程,我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while (number == 0) {
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程,我-1完毕了
this.notifyAll();
}
}
这里消费者只有两个,但当消费者数量>2时,就会出现问题
当资源类中方法中判断为if时,执行结果:很明显出现问题了
当把if改为while时,又没有什么问题了,这是为什么呢?
这种现象叫作 虚假唤醒,首先来看看官方文档
代码例子实现:
package sx;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: juc
* @description
* @author: 不会编程的派大星
* @create: 2021-04-21 11:03
**/
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
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();
}
}
// 判断等待,业务,通知
class Data2 {
// 数字 资源类
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await(); // 等待
//condition.signalAll(); // 唤醒全部
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
// 业务代码
while (number != 0) {
// 等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程,我+1完毕了
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number == 0) {
// 等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程,我-1完毕了
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
运行结果:
有小伙伴可能会有这个疑问,那既然都可以用,那这个juc的版本又有什么用呢?
任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,是有其对旧技术的优势和补充!
Condition 精准的通知和唤醒线程
问题:ABCD线程 抢占执行的顺序是随机的,如果想让ABCD线程有序执行,该如何改进代码?
期望实现的效果: A 执行完调用B,B执行完调用C,C执行完调用A
代码:
package sx;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: juc
* @description
* @author: 不会编程的派大星
* @create: 2021-04-21 11:09
**/
/*
* A 执行完调用B,B执行完调用C,C执行完调用A
*/
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printC();
}
}, "C").start();
}
}
class Data3 {
// 资源类 Lock
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
// number=1 A执行 number=2 B执行 number=3 C执行
public void printA() {
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number != 1) {
// A等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAAAAAA");
// 唤醒,唤醒指定的人,B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number != 2) {
// B等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBBBBBBBB");
// 唤醒,唤醒指定的人,c
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
// 业务,判断-> 执行-> 通知
// 业务,判断-> 执行-> 通知
while (number != 3) {
// C等待
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "=>CCCCC ");
// 唤醒,唤醒指定的人,A
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}