JUC-生产者和消费者问题
生产者和消费者问题
生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
解决办法
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
-
synchronized版
/** * 线程之间的通信问题,生产者和消费者问题 等待唤醒,通知唤醒 * 线程交替执行 A B同时操作同一个变量 */ public class BuyChicken { public static void main(String[] args) { Chicken chicken = new Chicken(); new Thread(()-> { for (int i = 0; i <20; i++) { try { chicken.push(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); new Thread(()-> { for (int i = 0; i < 20; i++) { try { chicken.pop(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); } } class Chicken{ private int count = 0; public synchronized void push() throws InterruptedException { //如果容器满了,就需要等待消费者消费 //线程可以在没有被通知,中断或超时的情况下唤醒 ,即所谓的虚假唤醒 //此处要注意,使用if语句存在虚假唤醒问题,要使用while进行判断 while (count == 10){ //通知消费者消费,生产等待 this.wait(); } //如果没有满,就需要丢入产品 count++; System.out.println(Thread.currentThread().getName()+"=>"+count); //可以通知消费者消费 this.notifyAll(); } //消费者消费产品 public synchronized void pop() throws InterruptedException { //判断能否消费 while(count == 0){ //等待生产者生产,消费者等待 this.wait(); } //如果可以消费 count--; System.out.println(Thread.currentThread().getName()+"=>"+count); //吃完了,通知生产者生产 this.notifyAll(); } }
-
Lock版
查看API可知,Lock相比于synchronized操作更加广泛,结构更加灵活
这里对比一下Synchronized和Lock的区别
- synchronized 内置的java关键字,Lock是一个java类
- synchronized 无法判断获取锁的状态 Lock可以判断是否获取到了锁
- synchronized 会自动释放锁,lock必须要手动释放锁,如果不释放锁,死锁
- synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等),Lock锁就不一定会等待下去
- synchronized 可重入锁,不可以中断的,非公平 Lock,可重入锁,可以判断锁,非公平(可以自己设置)
- synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码
Lock版的生产者和消费者模式
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BuyChicken {
public static void main(String[] args) {
Chicken1 chicken = new Chicken1();
new Thread(()-> {
for (int i = 0; i <20; i++) {
try {
chicken.push();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()-> {
for (int i = 0; i < 20; i++) {
try {
chicken.pop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Chicken1{
private int count = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void push() throws InterruptedException {
lock.lock();
try {
//如果容器满了,就需要等待消费者消费
while (count == 10){
//通知消费者消费,生产等待
condition.await();
}
//如果没有满,就需要丢入产品
count++;
System.out.println(Thread.currentThread().getName()+"=>"+count);
//可以通知消费者消费
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//消费者消费产品
public void pop() throws InterruptedException {
lock.lock();
try {
//判断能否消费
while(count == 0){
//等待生产者生产,消费者等待
condition.await();
}
//如果可以消费
count--;
System.out.println(Thread.currentThread().getName()+"=>"+count);
//吃完了,通知生产者生产
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
-
Condition实现精准通知唤醒
Condition实现可以保证通知排序,或者在执行通知时不需要保持锁定
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; 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(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); Condition condition3 = lock.newCondition(); private int number = 1; public void printA(){ lock.lock(); try { while(number != 1){ condition1.await(); } System.out.println(Thread.currentThread().getName() + "=>AAAAAAAAAA"); number = 2; condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB(){ lock.lock(); try {//业务:判断-执行-通知 while (number != 2){ condition2.await(); } System.out.println(Thread.currentThread().getName() + "=>BBBBBBBBBB"); number = 3; condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC(){ lock.lock(); try { while (number != 3){ condition3.await(); } System.out.println(Thread.currentThread().getName() + "=>CCCCCCCCCC"); number = 1; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }