欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java多线程 - (三) 线程间的通信(协作) - 生产者/消费者模式

程序员文章站 2022-04-23 08:37:21
...

        上一篇讲述了线程的互斥(同步),但是在很多情况下,仅仅同步是不够的,还需要线程与线程协作(通信),生产者/消费者模式是一个经典的线程同步以及通信的模型。

 

        假设有这样一种情况,有一个篮子,篮子里只能放一个鸡蛋,A线程专门往篮子里放鸡蛋,如果篮子里有鸡蛋,则一直等到篮子里没鸡蛋,B线程专门从篮子里取鸡蛋,如果篮子里没鸡蛋,则一直等到篮子里有鸡蛋。这里篮子是一个互斥区,每次放鸡蛋是互斥的,每次取鸡蛋也是互斥的,A线程放鸡蛋,如果这时B线程要取鸡蛋,由于A没有释放锁,B线程处于等待状态,进入阻塞队列,放鸡蛋之后,要通知B线程取鸡蛋,B线程进入就绪队列,反过来,B线程取鸡蛋,如果A线程要放 鸡蛋,由于B线程没有释放锁,A线程处于等待状态,进入阻塞队列,取鸡蛋之后,要通知A线程放鸡蛋,A线程进入就绪队列。我们希望当篮子里有鸡蛋时,A线程阻塞,B线程就绪,篮子里没鸡蛋时,A线程就绪,B线程阻塞,代码如下

/**
 * 
 */
package com.wsheng.thread.communication;

/**
 * 鸡蛋类
 * 
 * @author Wang Sheng(Josh)
 *
 */
public class Egg {

	private double weight;
	
	private double price;

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}
	
	

}

 

/**
 * 
 */
package com.wsheng.thread.communication;

import java.util.ArrayList;
import java.util.List;

/**
 * 线程间的通信: 放鸡蛋和取鸡蛋 - 生产者和消费者
 * @author Wang Sheng(Josh)
 *
 */
public class Basket {

	/** 共享资源:篮子 */
	private List<Egg> eggs = new ArrayList<Egg>();
	
	/** 取鸡蛋*/
	public synchronized Egg getEgg() {
		while (eggs.size() == 0) {
			try {
				wait(); // 当前线程进入阻塞队列
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		Egg egg = eggs.get(0);
		
		// 清空篮子
		eggs.clear();
		
		// 唤醒阻塞队列的某线程到就绪队列
		notify();
		
		System.out.println("拿到鸡蛋");
		return egg;
	}
	
	/** 放鸡蛋 */
	public synchronized void putEgg(Egg egg) {
		while (eggs.size() > 0) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		eggs.add(egg);
		
		// 唤醒阻塞队列的某线程到就绪队列
		notify();
		
		System.out.println("放入鸡蛋");
	}
	
	static class PutThread extends Thread {
		private Basket basket;
		private Egg egg = new Egg();
		
		public PutThread(Basket basket) {
			this.basket = basket;
		}
		
		public void run() {
			basket.putEgg(egg);
		}
	}
	
	static class GetThread extends Thread {
		private Basket basket;
		public GetThread(Basket basket) {
			this.basket = basket;
		}
		
		public void run() {
			basket.getEgg();
		}
	}
	
	
	
	public static void main(String[] args) {
		Basket basket = new Basket();
		for (int i = 0; i < 10; i++) {
			new PutThread(basket).start();
			new GetThread(basket).start();
		}

	}

}

 输出结果:

放入鸡蛋

拿到鸡蛋

放入鸡蛋

拿到鸡蛋

放入鸡蛋

拿到鸡蛋

放入鸡蛋

拿到鸡蛋

放入鸡蛋

拿到鸡蛋

放入鸡蛋

拿到鸡蛋

放入鸡蛋

拿到鸡蛋

放入鸡蛋

拿到鸡蛋

放入鸡蛋

拿到鸡蛋

 

        程序开始,A线程判断篮子是否为空,放入一个鸡蛋,并且唤醒在阻塞队列的一个线程,阻塞队列为空;假设CPU又调度了一个A线程,篮子非空,执行等待,这 个A线程进入阻塞队列;然后一个B线程执行,篮子非空,取走鸡蛋,并唤醒阻塞队列的A线程,A线程进入就绪队列,此时就绪队列就一个A线程,马上执行,放 入鸡蛋;如果再来A线程重复第一步,再来B线程重复第二步,整个过程就是生产者(A线程)生产鸡蛋,消费者(B线程)消费鸡蛋。

 

        这个例子中,同步(互斥)的资源是一个实实在在的篮子对象,其实,共享资源也可以是一个简单的变量,如下面的例子,共享资源是一个bool变量。

       例: 子线程循环10次,然后主线程循环100次,如此循环100次。子循环执行时主循环不能执行,主循环执行时子循环也不能执行。

   

/**
 * 
 */
package com.wsheng.thread.synchronize;

/**
 * @author Wang Sheng(Josh)
 *
 */
public class LoopThreadTest {
	
	public static void main(String[] args) {
		final Loop loopObj = new Loop();
		
		new Thread(new Runnable() {
			
			public void run() {
				execute(loopObj, "sub");
			}
		}).start();
		
		execute(loopObj, "main");

	}
	
	public static void execute(Loop loop, String threadType) {
		for (int i = 0; i < 100; i++) {
			try {
				if ("main".equals(threadType)) {
					loop.mainThread(i);
				} else {
					loop.subThread(i);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}
class Loop {
	private boolean isSubThread = true;
	
	public synchronized void mainThread(int loop) throws InterruptedException {
		while (isSubThread) {
			this.wait();
		}
		
		for (int i = 0; i < 100; i++) {
			System.out.println("main thread sequence of " + i + ", loop of " + loop);
		}
		
		isSubThread = true; // 主线程执行完毕,由子线程来执行
		this.notify(); // 唤醒阻塞队列中的子线程到就绪队列
	}
	
	
	public synchronized void subThread(int loop) throws InterruptedException {
		while (!isSubThread) {
			this.wait();
		}
		
		for (int i = 0; i < 10; i++) {
			System.out.println("sub thread sequence of " + i + ", loop of " + loop);
		}
		
		isSubThread = false; //  子线程执行完毕,由主线程来执行
		this.notify(); // 唤醒阻塞队列中的主线程到就绪队列
	}
}

     

 

需要注意的是,在上面的例子中,在调用wait方法时,都是用while判断条件的,而不是if,在wait方法说明中,也推荐使用while,因为在某些特定的情况下,线程有可能被假唤醒,使用while会循环检测更稳妥。