当前位置:首页 » 编程语言

【Java笔记】——多线程同步机制模拟生产者/消费者模型

2015-09-15 16:52 本站整理 浏览(221)

上篇介绍线程,说到线程同步机制,其中说到加锁的机制,如果加锁不合理,则会产生“死锁”。如果加锁的位置合理,则会解决多线程访问同一数据的问题。多线程访问的问题,其中很典型的一个模型就是生产者/消费者模型,下面就简单介绍一下多线程同步如何模拟生产者/消费者模型。

生产者/消费者模型理解起来并不难,就如社会中的生产消费一样,总要保持一个平衡。生产者需要生产产品,消费者需要消费产品,但是生产者不能不断生产,导致社会中产品偏多;消费者也不能一直消费产品,如果产品被消费完了,则同样会导致不安稳。所以消费者消费和生产者生产之间就得保持平衡,那么线程同步就解决了这个问题。

现在假设有一个容器,生产者生产的产品都放在容器中,消费者消费产品就是从容器中取出。现在生产者和消费者就是两个线程,两个线程会同时访问容器,如果不让容器放满导致产品无法放入,又不让容器为空导致消费者无法消费,就得对容器进行加锁。如果容器为空,则不让消费者线程访问,让生产者去生产;容器满的时候,则不让生产者访问,让消费者去消费,这也就是线程同步。

<span style="font-family:KaiTi_GB2312;font-size:18px;">public class ProducerConsumer {
	public static void main(String[] args) {
		SyncStack ss = new SyncStack();
		Producer p = new Producer(ss);
		Consumer c = new Consumer(ss);   //p , c两个线程同时访问ss这一个容器 
		new Thread(p).start();
		new Thread(p).start();
		new Thread(p).start();
		new Thread(c).start();
	}
}

//产品类
class WoTou {
	int id; 
	WoTou(int id) {
		this.id = id;
	}
	public String toString() {
		return "WoTou : " + id;
	}
}

//容器类
class SyncStack {
	int index = 0;
	WoTou[] arrWT = new WoTou[6];
	
	//生产者生产的产品放到容器中
	public synchronized void push(WoTou wt) { //此处是给向容器中放产品的push()方法加锁,关键字为synchronized
		while(index == arrWT.length) {
			try {
				this.wait();    //生产者生产的产品填满容器后,该生产的线程要等着,此时释放该对象的锁,让消费的线程访问
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notifyAll();		//生产者生产的产品填满容器后,通知其他线程,也就是通知消费的线程进行操作
		arrWT[index] = wt;
		index ++;
	}
	
	//消费者从容器中取出产品进行消费
	public synchronized WoTou pop() {       //此处是给从容器中取产品的pop()方法加锁,关键字为synchronized
		while(index == 0) {
			try {
				this.wait();    //消费者消费完容器中的产品后,该线程要等着,此时释放该对象的锁,让生产的线程访问
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.notifyAll();               //消费者消费完容器中的产品后,通知其他线程,也就是通知生产的线程进行操作
		index--;
		return arrWT[index];
	}
}

//生产者的线程
class Producer implements Runnable {
	SyncStack ss = null;
	Producer(SyncStack ss) {
		this.ss = ss;
	}
	
	//线程启动
	public void run() {
		for(int i=0; i<20; i++) {
			WoTou wt = new WoTou(i);
			ss.push(wt);
System.out.println("生产了:" + wt);
			try {
				Thread.sleep((int)(Math.random() * 200));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}			
		}
	}
}

//消费者的线程
class Consumer implements Runnable {
	SyncStack ss = null;
	Consumer(SyncStack ss) {
		this.ss = ss;
	}
	
	//线程启动
	public void run() {
		for(int i=0; i<20; i++) {
			WoTou wt = ss.pop();
System.out.println("消费了: " + wt);
			try {
				Thread.sleep((int)(Math.random() * 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}			
		}
	}
}</span>
正如代码中所示,该模型中共有五个类,产品类、容器类、生产者线程、消费者线程以及功能测试类。生产者线程和消费者线程是实现了Runnable接口,并且在类里面声明了容器类的对象,各自都是循环着向容器类中放置产品或者取出产品。产品类就是给产品加上id标识,自然也简单不用再说。关键就是在容器类,容器类中既有生产者生产的产品放到容器中的方法,也有消费者从容器中取出产品进行消费的方法。而同步的机制就体现在这里,这两个方法加锁就是解决了多线程访问容器的问题。

在容器类中,有两个关键点,那就是this.wait()和this.notifyAll()。如注释中所示,生产者生产的产品填满容器后,该生产的线程要等着,此时释放容器的锁,让消费的线程访问,而生产者生产的产品填满容器后,通知其他线程,也就是通知消费的线程进行操作。消费者消费完容器中的产品后,该线程也要等着,此时释放容器的锁,让生产的线程访问,消费者消费完容器中的产品后,通知其他线程,也就是通知生产的线程进行操作。

总结

生产者/消费者模型是典型的多线程访问,如果不加锁每个线程可随意访问,则会导致资源错乱,这样每个线程访问资源后得到的都不是自己想要的结果。线程同步机制就是为了避免这种问题,其实本质就是在同一时间只让一个线程访问,其他的等待即可。