`

消费者与生产者

阅读更多

多线程同步之生产者---消费者模型

线程同步是个老生常谈的问题了,在这里我将通过一个Java多线程程序,来说明控制相互交互的线程之间的运行进度,使程序运行总是既高效又稳定。这个多线程程序将采用生产者---消费者模型,来说明怎么样实现多线程的同步。

如果让我定义一下什么是消费者、生产者:我觉得可以把系统中使用某种资源的线程称为消费者,产生该种资源的线程称为生产者。在下面的Java的应用程序中,生产者线程向一个线程安全的堆栈缓冲区中写(PUSH)数据,消费者从该堆栈缓冲区中读(POP)数据,这样,这个程序中同时运行的两个线程共享同一个堆栈缓冲区资源。

类Producer是生产者模型,其中的run方法中定义了生产者线程所做的操作,循环调用push()方法,将生产的100个字母送入堆栈中,每次执行完push操作后,调用sleep方法睡眠一段随机时间。

类Consumer是消费者模型,循环调用pop方法,从堆栈取出一个字母,一共取100次,每次执行完push操作后,调用sleep方法睡眠一段随机时间。

先看下同步堆栈类的源码:

 

package ProducerAndConsumer;

public class SyncStack {
	
	private int index = 0;
	private char[] data;
	private int size = 100;
	
	public SyncStack(int size){
		System.out.println("栈被创建");
		this.size = size;
		data = new char[size];
	}
	
	public synchronized void push(char c){
		while( index == size){
			try{
				System.out.println("栈满了!");
				this.wait();//等待,直到有数据出栈
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		data[index] = c;
		index++;
		System.out.println("Produced:"+c);
		this.notify();//通知其它线程把数据出栈
	}
	
	public synchronized char pop(){
		while(index == 0){
			try{
				System.out.println("栈空了");
				this.wait();//等待其它线程把数据入栈
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		index--;//指针向下移动
		char ch = data[index];
		System.out.println("Consumed:"+ch);
		this.notify();//通知其它线程把数据入栈
		return ch;
	}
	
	public synchronized void print(){
		for(int i=0; i<index; i++)
		{
			System.out.println(data[i]);
		}
		System.out.println();
		this.notify();//通知其它线程显示堆栈内容
	}
}

 

有了同步堆栈类,接着看看我们的生产者类的源码:

package ProducerAndConsumer;
public class Producer implements Runnable {
	private SyncStack theStack;

	public Producer(SyncStack s) {
		theStack = s;
	}

	public void run() {
        char ch;
        for(int i=0; i<100; i++){
        	//随机产生100个字符
        	ch = (char)(Math.random()*26 + 'A');
        	theStack.push(ch);
        	
        	try{
        		Thread.sleep((int)(Math.random()*1000));
        	}catch(InterruptedException e){
        		e.printStackTrace();
        	}
        }
	}
}

 

接着看下消费者类的源码:

package ProducerAndConsumer;

public class Consumer implements Runnable {
	private SyncStack theStack;
	
	public Consumer(SyncStack s){
		theStack = s;
	}

	public void run() {
		char ch;
		for(int i=0; i<100; i++){
			ch = theStack.pop();
		
		
		try{
			//每产生一个字符线程就睡眠一下
			Thread.sleep((int)(Math.random()*1000));
		}catch(InterruptedException e)
		{
			e.printStackTrace();
		}
		}
		
	}
	


}

 

最后让我们来看看主程序main所在类的源码:

package ProducerAndConsumer;

public class SyncTest {
public static void main(String[] args) {
	SyncStack stack = new SyncStack(5);
	Runnable source = new Producer(stack);
	Runnable sink = new Consumer(stack);
	
	Thread t1 = new Thread(source);
	Thread t2 = new Thread(sink);
	
	t1.start();
	t2.start();
}
}

 

在本例中,使用了一个生产者线程和一个消费者线程,当生产者线程往堆栈中添加字符时,如果堆栈已满,通过调用this.wait方法,(这里,this就是互斥锁)把自己加入到互斥锁对象(SyncStack)的锁等待队列中,如果该堆栈不满,则该生产者线程加入到互斥锁对象(SyncStack)的锁申请队列中,并且很快就被JVM取出来执行。当生产者线程在执行添加字符操作的时候,消费者是不能从中取出字符的,只能在等待队列中等待,当生产者添加完字符的时候,使用this.notify()(这里,this就是互斥锁)把等待队列中的第一个消费者唤醒,把它加入到锁申请队列中,很快该消费者线程就会获得CPU时间。此时的生产者线程已经无法再次添加字符,因为消费者线程正在synchronized代码块中运行,JVM把生产者线程加入锁等待队列中。当消费者线程从堆栈中取完字符后,再使用this.notify()方法把生产者从等待进程中唤醒,添加字符,如此循环往复,直到生产者线程和消费者线程都运行结束。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics