java多线程设计模式之Producer-Consumer模式(一)

本文介绍了一种经典的生产者-消费者模式实现方案,通过构建线程安全的Table类作为两者之间的桥梁,确保多线程环境下数据交换的正确性和同步性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

     Producer是生产者的意思,指的是生成数据的线程,Consumer则是消费者的意思,指的是使用数据的线程,这里我们要实现的是如何安全的将数据从生产者交给消费者,看似简单但是当两者处于不同的线程时,处理速度差异便会产生问题。对于这个问题,我们可以在两者之间加入一个桥梁通道的角色,具体的设计如下:


情景创建,假如说有个蛋糕店,糕点师制作蛋糕,顾客吃蛋糕,糕点师制作完成后将蛋糕放在桌子上,桌子上做多可以放置三个蛋糕,如果桌子上满了就必须等到腾出位置,客人如果吃没了就要等待蛋糕重新放置到桌子上。

首先构建用于存放蛋糕的Table类:

public class Table {
	private final String[] buffer;
	private int tail;
	private int head;
	private int count;
	public Table(int count){
		this.buffer = new String[count];
		this.head=0;
		this.tail=0;
		this.count=0;
	}
	
	//防止蛋糕
	public synchronized void put(String cake) throws InterruptedException{
		System.out.println(Thread.currentThread().getName()+ " puts "+cake);
		//守护条件,当条件成立时,执行wait()
		while(count>=buffer.length){
			wait();
		}
		buffer[tail]=cake;
		tail=(tail+1)%buffer.length;
		count++;
		notifyAll();
	}
	
	//拿取蛋糕
	public synchronized String take()throws InterruptedException{
		//守护条件,当条件成立时,执行wait()
		while(count<=0){
			wait();
		}
		String cake = buffer[head];
		head = (head+1)%buffer.length;
		count--;
		//notifyAll()方法调用之后会将原来wait()方法等待的线程唤醒
		//继续执行noifyAll()方法后面的语句,执行notifyAll方法时必须获取对应的实例的锁
		notifyAll();
		System.out.println(Thread.currentThread().getName()+" takes "+cake);
		return cake;
	}

}
     其中对cake操作的方法:get和put都是用synchronized修饰,保证线程的安全性,同时通过构造函数来传递所要构建的Table 的容量。其中也是用到了之前将讲到的GuardSuspension模式,设置while语句对队列中的内容的判断作为守护条件。

用于表示蛋糕制作的MakerThread类:

public class MakerThread extends Thread {
	private final Random random;
	private final Table table;
	private static int id=0;
	
	public MakerThread(String name,Table table,long seed){
		super(name);
		this.table=table;
		this.random=new Random(seed);
	}
	
	public void run(){
		try{
			while(true){
				Thread.sleep(random.nextInt(1000));
				String cake ="[ cake No."+nextId() + " by "+getName() + "]";
				table2.put(cake);
			}
		}catch(InterruptedException e){
			
		}
		
		
	}
	//保证修改公用字段id在多个线程修改的时候能够保持互斥性
	private static synchronized int nextId(){
		return id++;
	}

}
   此类适用于向Table中添加蛋糕的,同时,再run()方法中设置sleep方法来模拟写入的耗时过程;类的初始化需要传入一个共享的Table类的实例,所以Table类要设计为线程安全的类才可以。这样就实现了往Table中添加cake的过程。

     下面模拟客人拿取蛋糕的过程,构建了一个EaterThread类:

public class EaterThread extends Thread {
	private final Table table;
	private final Random random;
	public EaterThread(String name ,Table table,long seed){
		super(name);
		this.table = table;
		this.random = new Random(seed);
	}
	
	public void run(){
		try{
			while(true){
				String cake = table2.take();
				Thread.sleep(random.nextInt(1000));
				
			}
		}catch(InterruptedException e){
			
		}
	}

}

   这个类模拟了对蛋糕的获取的行为,同样的类的初始化需要传入一个共享的Table类的实例,这就看出了Table类的安全性对整个的模拟过程的重要性,Table类起到了一个桥梁的角色,而且含包含了对数据的操作的方法,所以重点则是对于Table类的设计。

     下面编写一个main函数来测试一下:

public class ProducerConsumerMain {

	public static void main(String[] args) {
		Table2 table2 = new Table2(3);
		new MakerThread("MakerThread-1",table2,31415).start();
		new MakerThread("MakerThread-2",table2,92653).start();
		new MakerThread("MakerThread-3",table2,58979).start();
		new EaterThread("EaterThread-1",table2,32384).start();
		new EaterThread("EaterThread-2",table2,62643).start();
		new EaterThread("EaterThread-3",table2,38327).start();
		
	}

}
    模拟了3个糕点师和3客人的情况,所以各启动了3个线程,执行结果如下:


     关于Channel角色存在的意义,因为正因为有了Channel角色的存在才使得整个过程能够协调有序的进行,所以在这里我们应该这么考虑:线程的协调运行系要考虑放在中间的东西,同时应该注意应该保护的东西。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值