黑马程序员技术交流社区

标题: 【成都校区】Java实现生产者消费者的三种方式 [打印本页]

作者: 小刀葛小伦    时间: 2019-8-8 17:43
标题: 【成都校区】Java实现生产者消费者的三种方式
一、使用synchronize以及wait()、notify() /notifyAll()
[Java] 纯文本查看 复制代码
package com.zhb.juc;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 使用synchronize wait notify/notifyall实现生产者消费者模式
*/
class ShareDataV1 {
public static AtomicInteger atomicInteger = new AtomicInteger();
public volatile boolean flag = true;
public static final int MAX_COUNT = 10;
public static final List<Integer> pool = new ArrayList<>();
public void produce() {
// 判断,干活,通知
while (flag) {
// 每隔 1000 毫秒生产一个商品
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
synchronized (pool) {
//池子满了,生产者停止生产
//埋个坑,这里用的if
//TODO 判断
if (pool.size() == MAX_COUNT) {
try {
System.out.println("pool is full, wating...");
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//干活
pool.add(atomicInteger.incrementAndGet());
System.out.println("produce number:" + atomicInteger.get() + "\t" + "current size:" + pool.size());
//通知
pool.notifyAll();
}
}
}
public void consumue() {
// 判断,干活,通知
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
synchronized (pool) {
//池子满了,生产者停止生产
//埋个坑,这里用的if
//TODO 判断
if (pool.size() == 0) {
try {
System.out.println("pool is empty, wating...");
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//干活
int temp = pool.get(0);
pool.remove(0);
System.out.println("cousume number:" + temp + "\t" + "current size:" + pool.size());
//通知
pool.notifyAll();
}
}
}
public void stop() {
flag = false;
}
}
public class ProducerConsumer_V1 {
public static void main(String[] args) {
ShareDataV1 shareDataV1 = new ShareDataV1();
new Thread(() -> {
shareDataV1.produce();
}, "AAA").start();
new Thread(() -> {
shareDataV1.consumue();
}, "BBB").start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
shareDataV1.stop();
}
}

上面的程序在只有两个线程时(一个生产者,一个消费者)可以正常工作。打印的log如下:
produce number:1 current size:1
produce number:2 current size:2
produce number:3 current size:3
produce number:4 current size:4
produce number:5 current size:5
produce number:6 current size:6
produce number:7 current size:7
produce number:8 current size:8
produce number:9 current size:9
cousume number:1 current size:8
produce number:10 current size:9
produce number:11 current size:10
pool is full, wating...
cousume number:2 current size:9
produce number:12 current size:10
pool is full, wating...
cousume number:3 current size:9
produce number:13 current size:10
pool is full, wating...
cousume number:4 current size:9
produce number:14 current size:10
pool is full, wating...
cousume number:5 current size:9
produce number:15 current size:10
Process finished with exit code 0
但是我们把生产者和消费者线程扩展至多个。就出错了。例如再增加CCC和DDD线程分别生产和消费。只改动了main方法:
[Java] 纯文本查看 复制代码
public class ProducerConsumer_V1 {
public static void main(String[] args) {
ShareDataV1 shareDataV1 = new ShareDataV1();
new Thread(() -> {
shareDataV1.produce();
}, "AAA").start();
new Thread(() -> {
shareDataV1.consumue();
}, "BBB").start();
new Thread(() -> {
shareDataV1.produce();
}, "CCC").start();
new Thread(() -> {
shareDataV1.consumue();
}, "DDD").start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
shareDataV1.stop();
}
}

输出的log如下:
produce number:1 current size:1
produce number:2 current size:2
produce number:3 current size:3
produce number:4 current size:4
produce number:5 current size:5
produce number:6 current size:6
produce number:7 current size:7
produce number:8 current size:8
produce number:9 current size:9
produce number:10 current size:10
pool is full, wating...
pool is full, wating...
cousume number:1 current size:9
cousume number:2 current size:8
produce number:11 current size:9
produce number:12 current size:10
pool is full, wating...
pool is full, wating...
cousume number:3 current size:9
produce number:13 current size:10
produce number:14 current size:11
cousume number:4 current size:10
pool is full, wating...
pool is full, wating...
cousume number:5 current size:9
produce number:15 current size:10
produce number:16 current size:11
cousume number:6 current size:10
pool is full, wating...
pool is full, wating...
cousume number:7 current size:9
produce number:17 current size:10
produce number:18 current size:11
cousume number:8 current size:10
pool is full, wating...
pool is full, wating...
cousume number:9 current size:9
produce number:19 current size:10
produce number:20 current size:11
cousume number:10 current size:10
pool is full, wating...
pool is full, wating...
cousume number:11 current size:9
produce number:21 current size:10
produce number:22 current size:11
Process finished with exit code 0
我们看到current size 能到11了。这肯定出错了。因为我们要求pool的最大容量为10。出现这个情况的原因是在多线程的环境下,要防止虚假唤醒。即判断条件不能用if,而是用while。接下来我们修改上面//TODO部分的代码,把if改成while再来测试。最终版正确的代码如下:
[Java] 纯文本查看 复制代码
package com.zhb.juc;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 使用synchronize wait notify/notifyall实现生产者消费者模式
*/
class ShareDataV1 {
public static AtomicInteger atomicInteger = new AtomicInteger();
public volatile boolean flag = true;
public static final int MAX_COUNT = 10;
public static final List<Integer> pool = new ArrayList<>();
public void produce() {
// 判断,干活,通知
while (flag) {
// 每隔 1000 毫秒生产一个商品
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
synchronized (pool) {
//池子满了,生产者停止生产
//埋个坑,这里用的if
//TODO 判断
while (pool.size() == MAX_COUNT) {
try {
System.out.println("pool is full, wating...");
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//干活
pool.add(atomicInteger.incrementAndGet());
System.out.println("produce number:" + atomicInteger.get() + "\t" + "current size:" + pool.size());
//通知
pool.notifyAll();
}
}
}
public void consumue() {
// 判断,干活,通知
while (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
synchronized (pool) {
//池子满了,生产者停止生产
//埋个坑,这里用的if
//TODO 判断
while (pool.size() == 0) {
try {
System.out.println("pool is empty, wating...");
pool.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//干活
int temp = pool.get(0);
pool.remove(0);
System.out.println("cousume number:" + temp + "\t" + "current size:" + pool.size());
//通知
pool.notifyAll();
}
}
}
public void stop() {
flag = false;
}
}
public class ProducerConsumer_V1 {
public static void main(String[] args) {
ShareDataV1 shareDataV1 = new ShareDataV1();
new Thread(() -> {
shareDataV1.produce();
}, "AAA").start();
new Thread(() -> {
shareDataV1.consumue();
}, "BBB").start();
new Thread(() -> {
shareDataV1.produce();
}, "CCC").start();
new Thread(() -> {
shareDataV1.consumue();
}, "DDD").start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
shareDataV1.stop();
}
}

输出结果:
produce number:1 current size:1
produce number:2 current size:2
produce number:3 current size:3
produce number:4 current size:4
produce number:5 current size:5
produce number:6 current size:6
produce number:7 current size:7
produce number:8 current size:8
produce number:9 current size:9
produce number:10 current size:10
pool is full, wating...
pool is full, wating...
cousume number:1 current size:9
produce number:11 current size:10
pool is full, wating...
cousume number:2 current size:9
produce number:12 current size:10
pool is full, wating...
pool is full, wating...
cousume number:3 current size:9
produce number:13 current size:10
pool is full, wating...
cousume number:4 current size:9
produce number:14 current size:10
pool is full, wating...
pool is full, wating...
cousume number:5 current size:9
produce number:15 current size:10
pool is full, wating...
cousume number:6 current size:9
produce number:16 current size:10
pool is full, wating...
pool is full, wating...
cousume number:7 current size:9
produce number:17 current size:10
pool is full, wating...
cousume number:8 current size:9
produce number:18 current size:10
pool is full, wating...
pool is full, wating...
cousume number:9 current size:9
produce number:19 current size:10
pool is full, wating...
cousume number:10 current size:9
produce number:20 current size:10
cousume number:11 current size:9
Process finished with exit code 0
二、使用Lock,Condition的await和signal方法
JUC包下的锁Lock替代synchronize关键字。await方法代替wait,signal代替notifyall。
下面这个demo实现了pool的大小为1的生产者消费者模型。
[Java] 纯文本查看 复制代码
package com.zhb.juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws Exception {
lock.lock();
try {
while (number != 0) {
//等待,不能生产
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws Exception {
lock.lock();
try {
while (number == 0) {
//等待,不能消费
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ProducerConsumer_V2{
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
shareData.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "AA").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
shareData.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "BB").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
shareData.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "CC").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
shareData.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "DD").start();
}
}

三、终极版使用阻塞队列
首先谈谈阻塞队列:
当阻塞队列为空时,从阻塞队列中取数据的操作会被阻塞。
当阻塞队列为满时,往阻塞队列中添加数据的操作会被阻塞。
ArrayBlockingQueue:
基于数组的阻塞队列实现,其内部维护一个定长的数组,用于存储队列元素。线程阻塞的实现是通过ReentrantLock来完成的,数据的插入与取出共用同一个锁,因此ArrayBlockingQueue并不能实现生产、消费同时进行。而且在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
LinkedBlockingQueue:
基于单向链表的阻塞队列实现,在初始化LinkedBlockingQueue的时候可以指定对立的大小,也可以不指定,默认类似一个无限大小的容量(Integer.MAX_VALUE),不指队列容量大小也是会有风险的,一旦数据生产速度大于消费速度,系统内存将有可能被消耗殆尽,因此要谨慎操作。另外LinkedBlockingQueue中用于阻塞生产者、消费者的锁是两个(锁分离),因此生产与消费是可以同时进行的。


参考:https://www.jianshu.com/p/f4eefb069e27
下面是使用阻塞队列实现生产者消费者模式:
package com.zhb.juc;
/*
使用阻塞队列实现生产者消费者模型
*/
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
//资源类
class ShareDataV3{
private static final int MAX_CAPACITY = 10; //阻塞队列容量
private static BlockingQueue<Integer> blockingQueue= new ArrayBlockingQueue<>(MAX_CAPACITY); //阻塞队列
private volatile boolean FLAG = true;
private AtomicInteger atomicInteger = new AtomicInteger();
public void produce() throws InterruptedException {
while (FLAG){
boolean retvalue = blockingQueue.offer(atomicInteger.incrementAndGet(), 2, TimeUnit.SECONDS);
if (retvalue==true){
System.out.println(Thread.currentThread().getName()+"\t 插入队列"+ atomicInteger.get()+"成功"+"资源队列大小= " + blockingQueue.size());
}else {
System.out.println(Thread.currentThread().getName()+"\t 插入队列"+ atomicInteger.get()+"失败"+"资源队列大小= " + blockingQueue.size());
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName()+"FLAG变为flase,生产停止");
}
public void consume() throws InterruptedException {
Integer result = null;
while (true){
result = blockingQueue.poll(2, TimeUnit.SECONDS);
if (null==result){
System.out.println("超过两秒没有取道数据,消费者即将退出");
return;
}
System.out.println(Thread.currentThread().getName()+"\t 消费"+ result+"成功"+"\t\t"+"资源队列大小= " + blockingQueue.size());
Thread.sleep(1500);
}
}
public void stop() {
this.FLAG = false;
}
}
public class ProducerConsumer_V3 {
public static void main(String[] args) {
ShareDataV3 v3 = new ShareDataV3();
new Thread(()->{
try {
v3.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "AAA").start();
new Thread(()->{
try {
v3.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "BBB").start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
v3.stop();
}
}
可以看到使用阻塞队列根本不需要我们去加锁,通知什么的,完全解放了。
运行结果:
AAA 插入队列1成功资源队列大小= 0
BBB 消费1成功 资源队列大小= 0
AAA 插入队列2成功资源队列大小= 1
BBB 消费2成功 资源队列大小= 0
AAA 插入队列3成功资源队列大小= 1
BBB 消费3成功 资源队列大小= 1
AAA 插入队列4成功资源队列大小= 1
AAA 插入队列5成功资源队列大小= 2
BBB 消费4成功 资源队列大小= 1
AAA 插入队列6成功资源队列大小= 2
AAAFLAG变为flase,生产停止
BBB 消费5成功 资源队列大小= 1
BBB 消费6成功 资源队列大小= 0
超过两秒没有取道数据,消费者即将退出
Process finished with exit code 0




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2