什么是生产者消费者问题?
所谓的生产者消费者问题,是通过一个容器来解决生产者和消费者的强耦合问题。通俗的讲,就是生产者在不断的生产,消费者也在不断的消费,可是消费者消费的产品是生产者生产的,这就必然存在一个中间容器,我们可以把这个容器想象成是一个货架,当货架空的时候,生产者要生产产品,此时消费者在等待生产者往货架上生产产品,而当货架满的时候,消费者可以从货架上拿走商品,生产者此时等待货架的空位,这样不断的循环。那么在这个过程中,生产者和消费者是不直接接触的,所谓的‘货架’其实就是一个阻塞队列,生产者生产的产品不直接给消费者消费,而是仍给阻塞队列,这个阻塞队列就是来解决生产者消费者的强耦合的。就是生产者消费者问题。
那生产者消费者问题有什么用?
- 生产与消费的速度不匹配
- 软件开发过程中解耦
这篇文章通过JUC中的Lock接口来实现生产者消费者问题;
首先,我们得知道知道几个知识点:
Lock
lock锁是JDK1.5之后推出的一种锁机制,可以通过使用ReentrantLock实现类来实现实现线程之间的同步互斥,且在使用上比synchronized更加灵活。
查看JDK帮助文档,我们得知Lock锁的简单使用:
Condition
回忆 synchronized 关键字,它配合 Object 的 wait()、notify() 系列方法可以实现等待/通知模式。对于 Lock,通过 Condition 也可以实现等待/通知模式。Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。
查看JDK帮助文档,我们知道这个接口的实现类和使用方法:
当我们了解完上面几个知识点,这时候我们就可以去写一个生产者消费者问题例子:
/**
* 通过Lock锁来实现生产者和消费者问题
*/
public class ProducerAndConsumer2 {
public static void main(String[] args) {
ProAndCon2 proAndCon2 = new ProAndCon2();
new Thread(()->{
for (int i = 0; i < 5; i++) {
proAndCon2.Pro();
}
},"A").start();
new Thread(()->{ //让B线程去执行消费者方法
for (int i = 0; i < 5; i++) {
proAndCon2.Con();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
proAndCon2.Pro();
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
proAndCon2.Con();
}
},"D").start();
}
}
/**
* 在 Lock取代 synchronized方法和语句的使用,一个 Condition取代对象监视器的使用方法。
*/
class ProAndCon2{
private int num=0; //创建一个公共变量
//创建可重入锁对象
Lock lock=new ReentrantLock(); //可以取代synchronized
//创建Condition对象(用于监听)
Condition condition=lock.newCondition();//取代对象监视器的使用方法。 例如wait notifyAll等等
//生产者方法
public void Pro(){
//开启锁
lock.lock();
try {
while (num !=0){
condition.await();//等待
}
num++;
System.out.println(Thread.currentThread().getName()+"->"+num);
condition.signalAll();//唤醒所有线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//消费者方法
public void Con(){
//开启锁
lock.lock();
try {
while (num==0){
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+"->"+num);
condition.signalAll();//唤醒所有线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
结果如下:
这时候我们发现虽然可以去实现生产者消费者问题,但是我们想精准的唤醒执行,例如:A执行---->B执行---->C执行---->A执行---->B执行---->C…
我们依然可以通过Condition实现....
创建一个例子,依然通过Lock和Condition来实现:
通过Lock锁和Condition对象去实现一个生产者消费者问题的升级
A 唤醒->B 唤醒-> C 唤醒 ->A
*/
public class ProducerAndConsumer3 {
public static void main(String[] args) {
ProAndCon3 proAndCon3 = new ProAndCon3();
new Thread(()->{
for (int i = 0; i <5 ; i++) {
proAndCon3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <5 ; i++) {
proAndCon3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <5 ; i++) {
proAndCon3.printC();
}
},"C").start();
}
}
class ProAndCon3{
//创建Lock锁对象
Lock lock=new ReentrantLock();
//创建Condition对象
Condition condition1=lock.newCondition();
Condition condition2=lock.newCondition();
Condition condition3=lock.newCondition();
private int num=1;
//创建一个A执行方法
public void printA(){
lock.lock();
try {
while (num!=1){
//让condition1绑定到执行这个方法的线程上
condition1.await();//当num不等于1的时候,等待
}
System.out.println(Thread.currentThread().getName()+"->执行AAAAAAAAAAAAAAAAAAAAAAAAAA");
num=2;
condition2.signal();//唤醒绑定的线程2的condition对象
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//创建一个B执行方法
public void printB(){
lock.lock();
try {
while (num!=2){
//让condition1绑定到执行这个方法的线程上
condition2.await();//当num不等于1的时候,等待
}
System.out.println(Thread.currentThread().getName()+"->执行BBBBBBBBBBBBBB");
num=3;
condition3.signal();//唤醒绑定的线程2的condition对象
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//创建一个C执行方法
public void printC(){
lock.lock();
try {
while (num!=3){
//让condition1绑定到执行这个方法的线程上
condition3.await();//当num不等于1的时候,等待
}
System.out.println(Thread.currentThread().getName()+"->执行CCCCCCCCCCCCCCCCCCCCCCCCCC");
num=1;
condition1.signal();//唤醒绑定的线程2的condition对象
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
结果如下: