1 生产者和消费者模式概述
- 生产消费者问题,实际上主要是包含了两类线程:
一类是生产者线程用于生产数据
一类是消费者线程用于消费数据 - 为了耦合生产者和消费者的关系,通常会采用共享的数据区域,就像一个仓库
生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
消费者只需要从共享数据区中获取数据,并不需要关心生产者的行为
2 虚假唤醒问题
背景
一个卖面的面馆,有一个做面的厨师和一个吃面的食客,需要保证,厨师做一碗面,食客吃一碗面,不能一次性多做几碗面,更不能没有面的时候吃面;按照上述操作,进行三轮做面吃面的操作
代码
public class ThreadDemo4 {
public static void main(String[] args) throws Exception {
Noodles noodles = new Noodles();
new Thread(() -> {
for (int i = 1; i < 3; i++) noodles.makeNoodles();
}, "厨师A").start();
new Thread(() -> {
for (int i = 1; i < 3; i++) noodles.makeNoodles();
}, "厨师B").start();
new Thread(() -> {
for (int i = 1; i < 3; i++) noodles.eatNoodles();
}, "食客甲").start();
new Thread(() -> {
for (int i = 1; i < 3; i++) noodles.eatNoodles();
}, "食客已").start();
}
}
@Slf4j
class Noodles {
//面的数量
private int num = 0;
//做面方法
public synchronized void makeNoodles() {
//如果面的数量不为0,则等待食客吃完面再做面
if (num != 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
log.info(Thread.currentThread().getName() + "做好了一份面,当前有" + num + "份面");
//面做好后,唤醒食客来吃
this.notifyAll();
}
//吃面方法
public synchronized void eatNoodles() {
//如果面的数量为0,则等待厨师做完面再吃面
if (num == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
log.info(Thread.currentThread().getName() + "吃了一份面,当前有" + num + "份面");
//吃完则唤醒厨师来做面
this.notifyAll();
}
}
// 结果
22:37:06 [INFO ] [厨师A] c.i.juc.Noodles - 厨师A做好了一份面,当前有1份面
22:37:06 [INFO ] [食客已] c.i.juc.Noodles - 食客已吃了一份面,当前有0份面
22:37:06 [INFO ] [厨师B] c.i.juc.Noodles - 厨师B做好了一份面,当前有1份面
22:37:06 [INFO ] [食客甲] c.i.juc.Noodles - 食客甲吃了一份面,当前有0份面
22:37:06 [INFO ] [食客已] c.i.juc.Noodles - 食客已吃了一份面,当前有-1份面
22:37:06 [INFO ] [厨师A] c.i.juc.Noodles - 厨师A做好了一份面,当前有0份面
22:37:06 [INFO ] [食客甲] c.i.juc.Noodles - 食客甲吃了一份面,当前有-1份面
22:37:06 [INFO ] [厨师B] c.i.juc.Noodles - 厨师B做好了一份面,当前有0份面
原因分析
- 1 初始状态
- 2 厨师A得到操作权,发现面的数量为0,可以做面,面的份数+1,然后唤醒所有线程;
- 3 厨师B得到操作权,发现面的数量为1,不可以做面,执行wait操作;
- 4 厨师A得到操作权,发现面的数量为1,不可以做面,执行wait操作;
- 5 食客甲得到操作权,发现面的数量为1,可以吃面,吃完面后面的数量-1,并唤醒所有线程;
- 6 此时厨师A得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程
- 7 此时厨师B得到操作权了,因为是从刚才阻塞的地方继续运行,就不用再判断面的数量是否为0了,所以直接面的数量+1,并唤醒其他线程
解决办法
- 出现虚假唤醒的原因是从阻塞态到就绪态再到运行态没有进行判断,我们只需要让其每次得到操作权时都进行判断就可以了
if(num != 0){
this.wait();
}
改为
while(num != 0){
this.wait();
}
if(num == 0){
this.wait();
}
改为
while(num == 0){
this.wait();
}
3 使用Sychronized实现生产者和消费者
- 为了体现生产和消费过程总的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法就在Object类中Object类的等待和唤醒方法(隐式锁)
viod wait( ):导致当前线程等待,直到另一个线程调用该对象的notify()方法和notifyAll()方法
void notify( ):唤醒正在等待对象监视器的单个线程
void notifyAll( ):唤醒正在等待对象监视器的所有线程
(注意:wait、notify、notifyAll方法必须要在同步块或同步方法里且成对出现使用)
代码
/*
1.题目:
现在两个线程,可以操作初始值为0的一个变量,实现一个线程对该变量加1,
一个线程对该变量减1,交替执行,来10轮,变量的初始值为0
* */
@Slf4j
public class ThreadDemo1 {
public static void main(String[] args) throws Exception {
AirCondition airCondition = new AirCondition();
new Thread(() -> {
for (int i = 1; i < 4; i++) airCondition.increment();
}, "线程A").start();
new Thread(() -> {
for (int i = 1; i < 4; i++) airCondition.decrement();
}, "线程B").start();
new Thread(() -> {
for (int i = 1; i < 4; i++) airCondition.increment();
}, "线程C").start();
new Thread(() -> {
for (int i = 1; i < 4; i++) airCondition.decrement();
}, "线程D").start();
}
}
@Slf4j
class AirCondition {
private int number = 0;
public synchronized void increment() {
//1.判断
while (number != 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.干活
number++;
log.info(Thread.currentThread().getName() + ":" + number);
//3.唤醒
this.notifyAll();
}
public synchronized void decrement() {
while (number == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
log.info(Thread.currentThread().getName() + ":" + number);
this.notifyAll();
}
}
// 结果
19:50:43 [INFO ] [线程A] c.i.j.AirCondition - 线程A:1
19:50:43 [INFO ] [线程D] c.i.j.AirCondition - 线程D:0
19:50:43 [INFO ] [线程C] c.i.j.AirCondition - 线程C:1
19:50:43 [INFO ] [线程B] c.i.j.AirCondition - 线程B:0
19:50:43 [INFO ] [线程C] c.i.j.AirCondition - 线程C:1
19:50:43 [INFO ] [线程D] c.i.j.AirCondition - 线程D:0
19:50:43 [INFO ] [线程A] c.i.j.AirCondition - 线程A:1
19:50:43 [INFO ] [线程D] c.i.j.AirCondition - 线程D:0
19:50:43 [INFO ] [线程C] c.i.j.AirCondition - 线程C:1
19:50:43 [INFO ] [线程B] c.i.j.AirCondition - 线程B:0
19:50:43 [INFO ] [线程A] c.i.j.AirCondition - 线程A:1
19:50:43 [INFO ] [线程B] c.i.j.AirCondition - 线程B:0
4 使用ReentrantLock实现
ReentrantLock( ):创建一个ReentrantLock的实例
void lock( ):获得锁
void unlock( ):释放锁
public class ThreadDemo2 {
public static void main(String[] args) {
AirCondition2 airCondition = new AirCondition2();
new Thread(() -> {
for (int i = 0; i < 4; i++) airCondition.decrement();
}, "线程A").start();
new Thread(() -> {
for (int i = 0; i < 4; i++) airCondition.increment();
}, "线程B").start();
new Thread(() -> {
for (int i = 0; i < 4; i++) airCondition.decrement();
}, "线程C").start();
new Thread(() -> {
for (int i = 0; i < 4; i++) airCondition.increment();
}, "线程D").start();
}
}
@Slf4j
class AirCondition2 {
private int number = 0;
//定义Lock锁对象
final Lock lock = new ReentrantLock();
final Condition condition = lock.newCondition();
//生产者,如果number=0就 number++
public void increment() {
lock.lock();
try {
//1.判断
while (number != 0) {
try {
condition.await();//this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.干活
number++;
log.info(Thread.currentThread().getName() + ":\t" + number);
//3.唤醒
condition.signalAll();//this.notifyAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//消费者,如果number=1,就 number--
public void decrement() {
lock.lock();
try {
//1.判断
while (number == 0) {
try {
condition.await();//this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2.干活
number--;
log.info(Thread.currentThread().getName() + ":\t" + number);
//3.唤醒
condition.signalAll();//this.notifyAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
// 结果
20:13:39 [INFO ] [线程B] c.i.j.AirCondition2 - 线程B: 1
20:13:39 [INFO ] [线程C] c.i.j.AirCondition2 - 线程C: 0
20:13:39 [INFO ] [线程D] c.i.j.AirCondition2 - 线程D: 1
20:13:39 [INFO ] [线程A] c.i.j.AirCondition2 - 线程A: 0
20:13:39 [INFO ] [线程B] c.i.j.AirCondition2 - 线程B: 1
20:13:39 [INFO ] [线程C] c.i.j.AirCondition2 - 线程C: 0
20:13:39 [INFO ] [线程D] c.i.j.AirCondition2 - 线程D: 1
20:13:39 [INFO ] [线程A] c.i.j.AirCondition2 - 线程A: 0
20:13:39 [INFO ] [线程B] c.i.j.AirCondition2 - 线程B: 1
20:13:39 [INFO ] [线程C] c.i.j.AirCondition2 - 线程C: 0
20:13:39 [INFO ] [线程D] c.i.j.AirCondition2 - 线程D: 1
20:13:39 [INFO ] [线程A] c.i.j.AirCondition2 - 线程A: 0
20:13:39 [INFO ] [线程B] c.i.j.AirCondition2 - 线程B: 1
20:13:39 [INFO ] [线程C] c.i.j.AirCondition2 - 线程C: 0
20:13:39 [INFO ] [线程D] c.i.j.AirCondition2 - 线程D: 1
20:13:39 [INFO ] [线程A] c.i.j.AirCondition2 - 线程A: 0
5 使用ReentrantLock实现 精确通知
代码
/*
多个线程之间按顺序调用,实现A->B->C 三个线程启动,要求如下:
AA打印1次,BB打印2次,CC打印3次
AA打印1次,BB打印2次,CC打印3次
AA打印1次,BB打印2次,CC打印3次
....来3轮
* */
public class ThreadDemo3 {
public static void main(String[] args) {
ShareResource shareResource = new ShareResource();
new Thread(() -> {
for (int i = 1; i <= 3; i++) shareResource.print1();
}, "线程A").start();
new Thread(() -> {
for (int i = 1; i <= 3; i++) shareResource.print2();
}, "线程B").start();
new Thread(() -> {
for (int i = 1; i <= 3; i++) shareResource.print3();
}, "线程C").start();
}
}
@Slf4j
class ShareResource {
//设置一个标识,如果是number=1,线程A执行...
private int number = 1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void print1() {
lock.lock();
try {
//1.判断
while (number != 1) {
condition1.await();
}
//2.干活
for (int i = 1; i <= 1; i++) {
log.info(Thread.currentThread().getName() + ":\t" + i);
}
//3.唤醒
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print2() {
lock.lock();
try {
//1.判断
while (number != 2) {
condition2.await();
}
//2.干活
for (int i = 1; i <= 2; i++) {
log.info(Thread.currentThread().getName() + ":\t" + i);
}
//3.唤醒
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void print3() {
lock.lock();
try {
//1.判断
while (number != 3) {
condition3.await();
}
//2.干活
for (int i = 1; i <= 3; i++) {
log.info(Thread.currentThread().getName() + ":\t" + i);
}
//3.唤醒
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
// 结果
20:19:40 [INFO ] [线程A] c.i.j.ShareResource - 线程A: 1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B: 1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B: 2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C: 1
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C: 2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C: 3
20:19:40 [INFO ] [线程A] c.i.j.ShareResource - 线程A: 1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B: 1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B: 2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C: 1
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C: 2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C: 3
20:19:40 [INFO ] [线程A] c.i.j.ShareResource - 线程A: 1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B: 1
20:19:40 [INFO ] [线程B] c.i.j.ShareResource - 线程B: 2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C: 1
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C: 2
20:19:40 [INFO ] [线程C] c.i.j.ShareResource - 线程C: 3