生产者-消费者模式
- 生产者-消费者模式是一种经典的多线程设计模式,它为多线程之间的协作提供了良好的解决方案。利用共享内存缓冲区来进行通信。它有以下两个好处:
- 生产者和消费者之间不直接进行通讯,而是通过共享内存缓冲区,从而将生产者和消费者进行了解耦
- 由于共享内存缓冲区的存在,允许生产者和消费者在执行速度上存在时间差异,平衡了生产者和消费者的处理能力
实现方式
在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。
1. wait() / notify()方法
2. await() / signal()方法
3. BlockingQueue阻塞队列方法
4. PipedInputStream / PipedOutputStream
这里主要介绍第1,2和第3种
wait()/notify()方法
import java.util.LinkedList; import java.util.concurrent.TimeUnit; public class MyContainer1<T> { final private LinkedList<T> lists = new LinkedList<>(); final private int MAX = 10; //最多10个元素 private int count = 0; public synchronized void put(T t) { while(lists.size() == MAX) { //想想为什么用while而不是用if? try { this.wait(); //effective java } catch (InterruptedException e) { e.printStackTrace(); } } lists.add(t); ++count; this.notifyAll(); //通知消费者线程进行消费 } public synchronized T get() { T t = null; while(lists.size() == 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } t = lists.removeFirst(); count --; this.notifyAll(); //通知生产者进行生产 return t; } public static void main(String[] args) { MyContainer1<String> c = new MyContainer1<>(); //启动消费者线程 for(int i=0; i<10; i++) { new Thread(()->{ for(int j=0; j<5; j++) System.out.println(c.get()); }, "c" + i).start(); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //启动生产者线程 for(int i=0; i<2; i++) { new Thread(()->{ for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j); }, "p" + i).start(); } } }
await()和signal()方法
public class MyContainer2<T> { final private LinkedList<T> lists = new LinkedList<>(); final private int MAX = 10; //最多10个元素 private int count = 0; private Lock lock = new ReentrantLock(); private Condition producer = lock.newCondition(); private Condition consumer = lock.newCondition(); public void put(T t) { try { lock.lock(); while(lists.size() == MAX) { //想想为什么用while而不是用if? producer.await(); } lists.add(t); ++count; consumer.signalAll(); //通知消费者线程进行消费 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public T get() { T t = null; try { lock.lock(); while(lists.size() == 0) { consumer.await(); } t = lists.removeFirst(); count --; producer.signalAll(); //通知生产者进行生产 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return t; } public static void main(String[] args) { MyContainer2<String> c = new MyContainer2<>(); //启动消费者线程 for(int i=0; i<10; i++) { new Thread(()->{ for(int j=0; j<5; j++) System.out.println(c.get()); }, "c" + i).start(); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //启动生产者线程 for(int i=0; i<2; i++) { new Thread(()->{ for(int j=0; j<25; j++) c.put(Thread.currentThread().getName() + " " + j); }, "p" + i).start(); } } }
针对以上两种方法,面试中常问一下两个问题
为什么用notifyAll,而不用notify
以生产者为例,调用notify只能叫醒一个线程,该叫醒的线程可能还是生产者,但是本意是生产者生产了一个产品,让消费者唤醒来消费该产品,所以应该使用notifyAll,唤醒所有线程
- 判断时为什么用while而不用if 还是以生产者线程为例,当生产者发现内存缓冲区满的时候,调用wait()方法等待,当消费者消费掉一个产品后,生产者线程被唤醒,当一个生产者线程从wait()出准备执行时,另一个线程获得了锁,并成功执行,放入了一个产品;这时这个生产者线程拿到锁后,如果是if的话,直接从wait()后开始执行,发现已放不下,会抛出异常。但如果换成while,它会再执行判断,发现共享内存缓冲区已满,则又会等待
这里还想说下synchronized和ReenTrantLock之间的区别:
1. Synchronized可以用在方法上,也可用于代码块上;而ReenTrantLock只能用于特定代码块上 2. Synchronized不需要手动释放锁,而RennTrantLock既要手动上锁还要手动释放锁 3. ReenTrantLock等待可中断,当一个线程长期不释放锁时,正在等待的线程可以调用lockInterruptibly() 方法对该线程进行打断 4. Synchronized为非公平锁,而ReenTrantLock可以指定为公平锁,根据申请锁的时间依次获得锁 5. 一个ReenTrantLock可以同时绑定多个Condition对象,而Synchronized要和多于一个的条件关联时,要额外添加锁, 因此Condition能够更加精确的控制多线程的休眠与唤醒
阻塞队列的方法
producer
public class Producer implements Runnable{ BlockingQueue<String> queue; public Producer(BlockingQueue<String> queue){ this.queue = queue; } @Override public void run() { try{ String product_name = "a product-"+Thread.currentThread().getName(); System.out.println("I have produced:"+Thread.currentThread().getName()); queue.put(product_name); //如果队满,则阻塞 }catch (Exception e){ e.printStackTrace(); } } }
consumer
public class consumer implements Runnable{ BlockingQueue<String> blockingQueue; public consumer(BlockingQueue<String> blockingQueue){ this.blockingQueue = blockingQueue; } @Override public void run() { try { String product_name = blockingQueue.take(); //如果队空,则阻塞 System.out.println("消费了:"+product_name); } catch (InterruptedException e) { e.printStackTrace(); } } }
test
public class test_producer { public static void main(String[] args) { BlockingQueue<String> queue = new LinkedBlockingDeque<>(2); //BlockingQueue<String> queue = new ArrayBlockingQueue<String>(2); // LinkedBlockingDeque 默认大小为Integer.MAX_VALUE for (int i = 0; i < 5; i++) { new Thread(new Producer(queue), "Producer"+i).start(); new Thread(new consumer(queue), "Consumer"+i).start(); } } }