通过对JAVA多线程的学习,自己手写了三种方式,实现简单的一对一生产者消费者模式,每个案例中都有一个消费者线程,一个生产者线程和一个存储产品的类。具体如下。
PS:目前只实现了三种方法,如果有更多的,十分欢迎大家补充,一起交流。
1 采用wait()和notify()的结合方法
wait()和notify()需要结合sychronized使用,wait()负责释放锁,挂起线程,等待其他线程notify()方法唤醒该挂起的线程。具体代码如下:
package com.blog.Thread1;
/**
* 使用wait()和notify()实现一对一生产者消费者模式
*/
public class ThreadTest {
public static void main(String[] args) {
//创建用于存放产品的对象
Product product = new Product();
//创建生产者线程对象
Thread producer = new Producer(product);
//创建消费者线程对象
Thread customer = new Customer(product);
//必须先开启生产者,再开启消费者
producer.start();
customer.start();
}
}
//用字符串模拟产品
class Product{
// 用于存放产品,若为空字符串,代表没有产品
private String product = "";
//获得产品
public String getProduct() {
return product;
}
//生产产品
public void setProduct(String product){
this.product = product;
}
}
//模拟消费者
class Customer extends Thread{
private Product product;
//构造方法
public Customer(Product product){
this.product = product;
}
@Override
public void run() {
try {
//不断从Product中获取产品进行消费
while(true) {
//对product加锁
synchronized (product) {
if (product.getProduct().equals("")){
//空字符串代表还没有产品,就进行等待
product.wait();
}
//执行到这里说明生产者生产了产品,此时消费者可以拿到
String product_str = product.getProduct();
System.out.println("消费者:消费了产品--" + product_str);
//将product中的产品设置为空,表示消费者已经消费产品
product.setProduct("");
Thread.sleep(1000);//模拟延时的效果
product.notify();//通知生产者该生产产品了
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//模拟生产者
class Producer extends Thread{
private Product product;
//构造方法
public Producer(Product product){
this.product = product;
}
@Override
public void run() {
try {
//为了区别产品,辅助添加的变量
int i = 1;
//不断从Product中获取产品进行消费
while(true) {
//对product加锁
synchronized (product) {
if (!product.getProduct().equals("")){
//字符串不为空,说明消费者还没有消费产品
//那么就等待消费者消费之后再生产产品
product.wait();
}
//执行到这里说明消费者消费了产品,那么就生产产品
product.setProduct("产品" + i);
System.out.println("生产者:生产了产品--" + "产品" + i);
i ++;
Thread.sleep(1000); //模拟延时的效果
product.notify();//通知消费者消费产品
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
程序运行结果:
生产者:生产了产品--产品1
消费者:消费了产品--产品1
生产者:生产了产品--产品2
消费者:消费了产品--产品2
生产者:生产了产品--产品3
消费者:消费了产品--产品3
生产者:生产了产品--产品4
消费者:消费了产品--产品4
生产者:生产了产品--产品5
消费者:消费了产品--产品5
生产者:生产了产品--产品6
消费者:消费了产品--产品6
生产者:生产了产品--产品7
消费者:消费了产品--产品7
2 采用Condition实现
Condition需要结合Lock使用,因为只能通过Lock获取Condition对象。具体代码实现如下:
/**
* 使用Condition实现一对一生产者消费者模式
*/
public class ThreadTest {
public static void main(String[] args) {
//创建用于存放产品的对象
ProductService productService = new ProductService();
//创建生产者线程对象
Thread producer = new Producer(productService);
//创建消费者线程对象
Thread customer = new Customer(productService);
//必须先开启生产者,再开启消费者
producer.start();
customer.start();
}
}
//用字符串模拟产品
class ProductService{
//创建锁
Lock lock = new ReentrantLock();
//创建Condition对象
Condition condition = lock.newCondition();
// 用于存放产品,若为空字符串,代表没有产品
public String product = "";
//获得产品
public void getProduct() {
try {
lock.lock();
if (this.product.equals("")){
//如果product为空字符串,说明没有产品
//调用Condition的await()方法挂起线程
//等待生产者生产产品
condition.await();
}
//运行到这里说明获得了产品
System.out.println("消费者:消费了产品--" + this.product);
//将product设置为空
this.product = "";
condition.signal();//通知生产者生产产品
Thread.sleep(1000); //模拟延时效果
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//生产产品
public void setProduct(){
try {
lock.lock();
if (!this.product.equals("")){
//如果product不为空字符串,说明消费者还没有消费,
//那么挂起线程等待消费者消费产品
condition.await();
}
//运行到这里说明消费者消费了产品
//生产者生产产品 用Random产生随机数,区分不同的产品
this.product = "产品" + new Random().nextInt(100);
System.out.println("生产者:生产了产品--" + this.product);
condition.signal();//通知消费者消费产品
Thread.sleep(1000); //模拟延时效果
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
//模拟消费者
class Customer extends Thread{
private ProductService productService;
//构造方法
public Customer(ProductService product){
this.productService = product;
}
@Override
public void run() {
try {
//不断从Product中获取产品进行消费
while(true) {
productService.getProduct();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//模拟生产者
class Producer extends Thread{
private ProductService productService;
//构造方法
public Producer(ProductService product){
this.productService = product;
}
@Override
public void run() {
try {
//不断从Product中获取产品进行消费
while(true) {
productService.setProduct();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
程序运行结果:
生产者:生产了产品--产品23
消费者:消费了产品--产品23
生产者:生产了产品--产品46
消费者:消费了产品--产品46
生产者:生产了产品--产品23
消费者:消费了产品--产品23
生产者:生产了产品--产品96
消费者:消费了产品--产品96
生产者:生产了产品--产品49
消费者:消费了产品--产品49
生产者:生产了产品--产品37
消费者:消费了产品--产品37
生产者:生产了产品--产品88
消费者:消费了产品--产品88
3 使用SynchronousQueue实现
SynchronousQueue是一个不存储元素的的阻塞队列。SynchronousQueue中的每个put操作都必须等待一个take操作完成,否则不能继续向队列中添加元素。可以把它比喻成一个“快递员”,负责将生产者生产的产品直接递给消费者,非常适用于传递型场景,比如将一个线程的数据传递给另一个线程使用。SynchronousQueue的吞吐量高于LinkedBlockingQueue和ArrayBlockingQueue。具体使用代码如下:
/**
* 使用SynchronousQueue实现一对一生产者消费者模式
*/
public class ThreadTest {
public static void main(String[] args) {
//创建用于存放产品的对象
ProductService productService = new ProductService();
//创建生产者线程对象
Thread producer = new Producer(productService);
//创建消费者线程对象
Thread customer = new Customer(productService);
//必须先开启生产者,再开启消费者
producer.start();
customer.start();
}
}
//用字符串模拟产品
class ProductService{
//创建用于存放产品的一个队列
public SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
}
//模拟消费者
class Customer extends Thread{
private ProductService productService;
//构造方法
public Customer(ProductService product){
this.productService = product;
}
@Override
public void run() {
try {
//不断从Product中获取产品进行消费
while(true) {
//从队列中取出产品
String product = productService.synchronousQueue.take();
System.out.println("消费者:消费了产品--" + product);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//模拟生产者
class Producer extends Thread{
private ProductService productService;
//构造方法
public Producer(ProductService product){
this.productService = product;
}
@Override
public void run() {
try {
//不断从Product中获取产品进行消费
while(true) {
//生产产品放入队列中
String product = "产品" + new Random().nextInt(100);
productService.synchronousQueue.put(product);
System.out.println("生产者:生产了产品--" + product);
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
程序运行结果:
生产者:生产了产品--产品38
消费者:消费了产品--产品38
消费者:消费了产品--产品21
生产者:生产了产品--产品21
生产者:生产了产品--产品54
消费者:消费了产品--产品54
消费者:消费了产品--产品29
生产者:生产了产品--产品29
生产者:生产了产品--产品32
消费者:消费了产品--产品32
生产者:生产了产品--产品0
消费者:消费了产品--产品0
这个案例不必纠结最后生产者和消费者出现的先后顺序,因为代码是异步执行的,所以在执行到System.out.println()方法的时候,可能是消费者先抢到了锁,也可能是生产者先抢到了锁,但是生产者和消费者是成对出现的,而且在SynchronousQueue中,数据的put和take操作是成对存在的,是同步的,不会出现多线程问题。(补充:System.out.println方法源码中是sychronized修饰的方法,所以在同一时间,只能有一个线程使用该方法,所以这里才会出现生产者消费者顺序不一致问题,但是肯定是成对出现)。