生产者和消费者也是一个非常经典的多线程模式,我们在实际开发中应用非常广泛的思 想理念。在生产消费模式中:通常由两类线程,即若干个生产者的线程和若干个消费者的线 程。生产者线程负责提交用户请求,消费者线程则负责具体处理生产者提交的任务,在生产 者和消费者之间通过共享内存缓存区进行通信。
具体代码逻辑实现思路:
代码0:Main,外面使用的设计模式的类
package com.bjsxt.chapter17.demo03;
import java.util.concurrent.*;
/**
* @author whl
* @date 2021/2/24
*/
public class Main {
public static void main(String[] args) {
// 关键:生产者消费者共享一个数据缓冲区
BlockingQueue<Data> dataQueue=new LinkedBlockingQueue<>();
// 创建生产业务对象
Producer p1=new Producer(dataQueue);
Producer p2=new Producer(dataQueue);
// 创建消费业务对象
Consumer c1=new Consumer(dataQueue);
Consumer c2=new Consumer(dataQueue);
Consumer c3=new Consumer(dataQueue);
// 使用线程池里头的线程并启动
ExecutorService threadPool= Executors.newCachedThreadPool();
threadPool.execute(p1);
threadPool.execute(p2);
threadPool.execute(c1);
threadPool.execute(c2);
threadPool.execute(c3);
// 三秒后关闭生产操作
try{
TimeUnit.SECONDS.sleep(30);
}catch (Exception e){
e.printStackTrace();
}
p2.stop();
p1.stop();
}
}
代码1:Data
package com.bjsxt.chapter17.demo03;
/**
* @author whl
* @date 2021/2/24
*/
public class Data {
private int id;
private String name;
public int getId() {
return id;
}
public String getName() {
return name;
}
public Data(int id, String name) {
this.id = id;
this.name = name;
}
}
代码2:Producer
package com.bjsxt.chapter17.demo03;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author whl
* @date 2021/2/24
*/
/**
* 生产业务
*/
public class Producer implements Runnable{
// 关键:共享数据的缓冲区
private BlockingQueue<Data> dataQueue;
// 重点:停止生产的标记变量,
private volatile boolean isRunning=true;
// 线程池放进任务对象,每个对象要共享资源count,代表生产第几个数据
private static AtomicInteger count=new AtomicInteger(0);
// 生产者使用缓冲数据
public Producer(BlockingQueue<Data> dataQueue){
this.dataQueue=dataQueue;
}
@Override
public void run() {
while (isRunning){
try{
int i=count.incrementAndGet();
Data data=new Data(i,"data-"+i);
// 模拟生产时间:使用休眠
TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000));
if(!dataQueue.offer(data, ThreadLocalRandom.current().nextInt(1000), TimeUnit.MILLISECONDS)){
System.out.println("producer thread: "+Thread.currentThread().getName()+" submit data wrong,data id :"+data.getId()+"(produce)");
// 重新提交 等操作
}else{
System.out.println("producer thread: "+Thread.currentThread().getName()+" submit one data,data id :"+data.getId()+"(produce)"); }
}catch (Exception e){
e.printStackTrace();
}
}
}
// 将标记变量设置为fasle
public void stop(){
this.isRunning=false;
}
}
代码3:Consumer
package com.bjsxt.chapter17.demo03;
import java.sql.Time;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
/**
* @author whl
* @date 2021/2/24
*/
public class Consumer implements Runnable {
// 关键:共享数据的缓冲区引用变量
private BlockingQueue<Data> dataQueue;
// 设置引用变量
public Consumer(BlockingQueue<Data> dataQueue) {
this.dataQueue = dataQueue;
}
@Override
public void run() {
while (true){
try{
Data data = dataQueue.take();
// 模拟消费数据的时间
TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(1000));
System.out.println("consumer thread : "+Thread.currentThread().getName()+" consume data, data id is "+data.getId());
}catch (Exception e){
e.printStackTrace();
}
}
}
}
运行结果:
2个生产者线程30s生产了128个数据,3个消费者线程30s消费完毕.生产者线程60s后未执行会被vm回收,消费者线程进入了阻塞状态,等待数据生产消费。所以程序hang着。
jstack查看这6个线程运行状态:
15:53:40
main线程处于普通的BLOCKED状态
pool-1-thread-1,2,3,4,5线程由于模拟生产数据、消费数据,而陷入普通的阻塞状态
2021-02-24 15:53:44
main 线程由于执行sleep处阻塞状态
pool-Thread-1,2正在生产数据,处于普通的阻塞状态;
pool-Thread-3正在消费数据,处于普通的阻塞状态;
pool-Thread-4,5由于dataQueue.take()失败,没有数据要取数据陷入parking阻塞状态,等待被生产数据的线程唤醒。
2021-02-24 15:54:01
main线程仍然阻塞,30s还是挺长的hh
pool-Thread-1,2正在生产数据、3,4,5正在消费数据
2021-02-24 15:54:07
main线程执行完毕了!!30s过去了。那么pool-Thread-1,2这两个生产者线程的isRunning=false,会停止运行。
pool-Thread-1,2仍然存在!虚拟机栈帧中是看不懂的东西,线程池会销毁60s未执行的县城对象。
pool-Thread-3,4,5进入取不到数据而阻塞的wait队列阻塞状态。
2021-02-24 15:56:35
main线程已经寿终正寝。
54 07到现在56 35有两分钟了,按照语气java虚拟机栈中应该没有pool-Thread-1,2,事实与结果一致。
3,4,5这三个消费者县城等待被唤醒。
总结:
生产者业务对象和消费者业务对象有一个引用变量指向公共的数据缓冲区,在此次的实现中选择的是无界阻塞队列类型保存数据。数据也是一个对象。因此至少是需要设计三个类:生产者业务类,消费者业类,数据类。线程执行单元的两种方式,使用实现Runnabel接口方式。
除此之外,需要设计一个Main类,创建无界阻塞队列对象、生产者对象、消费者对象、线程池、调用execute启动5个线程。执行30s后关闭消费者两个线程。