多线程同步之生产者---消费者模型
线程同步是个老生常谈的问题了,在这里我将通过一个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()方法把生产者从等待进程中唤醒,添加字符,如此循环往复,直到生产者线程和消费者线程都运行结束。