Java面试——写一个生产者与消费者
一、通过synchronize 中的 wait 和 notify 实现
【1】我们可以将生产者和消费者需要的方法写在公共类中
1 package com.yintong.concurrent; 2 3 import java.util.LinkedList; 4 5 public class Concurrentcomm { 6 //常量 7 private static int MAX_VALUE = 10; 8 //可以理解为缓存 9 LinkedList<String> linkedList = new LinkedList<>(); 10 Object object = new Object(); 11 /* 12 * 生产者方法 13 */ 14 public void product() throws Exception { 15 synchronized(linkedList) { 16 while(MAX_VALUE == linkedList.size()) { 17 System.out.println("仓库已满,【生产者】: 暂时不能执行生产任务!"); 18 linkedList.wait(); 19 } 20 linkedList.push(" 李四 "); 21 System.out.println("【生产者】:生产了一个产品\t【现仓储量为】:" + linkedList.size()); 22 linkedList.notifyAll(); 23 } 24 } 25 /* 26 * 消费者方法 27 */ 28 public void customer() throws Exception { 29 /* 30 * 根据jdk的void notifyAll()的描述,“解除那些在该对象上调用wait()方法的线程的阻塞状态。该方法只能在同步方法或同步块内部调用。 31 * 如果当前线程不是对象所得持有者, 32 * 该方法抛出一个java.lang.IllegalMonitorStateException 异常” 33 * so我们使用同一把锁 34 */ 35 synchronized (linkedList) { 36 //多线程判断中使用 while 不要使用 if 否则会出现虚假唤醒问题 37 while(linkedList.size() == 0) { 38 System.out.println("仓库无货,【消费者】: 暂时不能执行消费任务!"); 39 linkedList.wait(); 40 } 41 linkedList.pop(); 42 System.out.println("【消费者】:消费了一个产品\t【现仓储量为】:" + linkedList.size()); 43 linkedList.notifyAll(); 44 } 45 } 46 }
【2】在 main 函数中调用生产者和消费者方法,并加限制即可
1 /** 2 * @author zzx 3 * @desc 生产者与消费者 4 * 5 */ 6 public class Concurrent { 7 //常量 8 private static int MAX_VALUE = 100; 9 10 public static void main(String[] args) { 11 Concurrentcomm con = new Concurrentcomm(); 12 new Thread(new Runnable() { 13 14 @Override 15 public void run() { 16 try { 17 for (int i = 0; i < MAX_VALUE; i++) { 18 Thread.sleep(0); 19 con.product(); 20 } 21 } catch (Exception e) { 22 // TODO Auto-generated catch block 23 e.printStackTrace(); 24 } 25 } 26 }).start(); 27 // 消费者 28 new Thread(new Runnable() { 29 30 @Override 31 public void run() { 32 try { 33 Thread.sleep(10); 34 for (int i = 0; i < MAX_VALUE; i++) { 35 con.customer(); 36 } 37 } catch (Exception e) { 38 e.printStackTrace(); 39 } 40 } 41 }).start(); 42 } 43 }
【3】简单的生产者与消费者模式就完成了,可以看下运行的结果
二、通过 Lock 中的 await 与 signalAll 实现
【1】我们将公共的属性和方法放在 Resouce 类中,在资源类中使用 Lock 中的 lock()进行加锁,控制并发操作。使用 await()方法阻塞线程。使用 signalAll()唤醒线程。
1 /** 2 * 通过 Lock 实现生产者与消费者 3 * 资源类:将公共的资源放在一个单独的类中,可以将其看做一个产品,自身就就有生产和消费的能力(方法) 4 */ 5 public class ProductAndConsumer { 6 public static void main(String[] args) { 7 Resouce resouce = new Resouce(); 8 //生产者 9 new Thread(()->{ 10 for (int i=1;i<=5;i++) { 11 resouce.product(); 12 } 13 },String.valueOf("生产者")) .start(); 14 15 //消费者 16 new Thread(()->{ 17 for (int i=1;i<=5;i++){ 18 resouce.consumer(); 19 } 20 },String.valueOf("消费者")).start(); 21 } 22 } 23 //资源类 24 class Resouce { 25 private int MAX_VALUE = 3; 26 private int MIN_VALUE = 0; 27 private int number = 0; 28 private Lock lock = new ReentrantLock(); 29 private Condition condition = lock.newCondition(); 30 31 //生产者 32 public void product(){ 33 try { 34 lock.lock(); 35 //如果生产的数量大于最大值则阻塞 36 while(number >= MAX_VALUE){ 37 condition.await(); 38 } 39 number++; 40 System.out.println("【生产者】:生产了一个产品\t【现仓储量为】:" + number); 41 condition.signalAll(); 42 } catch (InterruptedException e) { 43 e.printStackTrace(); 44 }finally { 45 lock.unlock(); 46 } 47 } 48 49 //消费者 50 public void consumer(){ 51 try { 52 lock.lock(); 53 //如果消费的值=0则阻塞 54 while(number <= MIN_VALUE){ 55 condition.await(); 56 } 57 number--; 58 System.out.println("【消费者】:消费了一个产品\t【现仓储量为】:" + number); 59 condition.signalAll(); 60 } catch (InterruptedException e) { 61 e.printStackTrace(); 62 }finally { 63 lock.unlock(); 64 } 65 } 66 }
【2】输出结果展示:
三、synchronized 和 Lock 的区别
【1】原始构成:synchronized 是关键字属于 JVM 层面。底层通过 monitorenter(进入)monitorexit(退出)实现。底层是通过 monitor 对象完成,其实 wait/notify 等方法也依赖于 monitor 对象,只有在同步块或方法中才能调用 wait/notify 等方法。Lock 是具体类(java.util.concurrent.locks.Lock)是 API 层面的锁。
【2】使用方法:synchronized 不需要用户手动释放锁,当 synchronized 代码执行完后,系统会自动释放锁。ReentrantLock 则需要用户手动释放锁,若未主动释放锁,就可能导致出现死锁的现象。
【3】等待是否中断:synchronized 不可中断,除非抛出异常或者正常运行完成。ReentrantLock 可中断,1)、设置超时时间 tryLock(long timeout,TimeUnit unit) 2)、lockInterruptibly() 放在代码块中,调用 interrupt() 方法可中断。
【4】加锁是否公平:synchronized 非公平锁。ReentrantLock 两者都可以,默认是非公平锁,构造方法可以传入 boolean 值,true 为公平锁,false 为非公平锁。
【5】锁绑定多个条件 Condition:synchronized 没有。ReentrantLock 用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像 synchronized 要么随机唤醒一个线程要么唤醒全部线程。
四、通过阻塞队列实现生产者与消费者
【1】通过blockQueue 中的 put/take 方法实现生产者与消费者,具体实现如下:当生产者使用put 生产到指定的队列大小3时,就会阻塞当前线程。这是消费者线程会通过 take 方法消费队列中的消息。当队列中没有消息时,会阻塞,直到有消息消费。
1 public class BlockProductConsumer { 2 public static void main(String[] args) { 3 MyResouce resouce = new MyResouce(new ArrayBlockingQueue(3)); 4 //生产者线程 5 new Thread(()->{ 6 for(int i=1;i<=10;i++){ 7 resouce.product(); 8 } 9 },"生产者").start(); 10 11 //消费者线程 12 new Thread(()->{ 13 for(int i=1;i<=10;i++){ 14 try { 15 resouce.consumer(); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 } 20 },"消费者").start(); 21 22 try { 23 TimeUnit.SECONDS.sleep(1); 24 resouce.stop(); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 } 29 } 30 31 32 /** 33 * 公共资源类 34 */ 35 class MyResouce{ 36 //标记 while 无限循环 37 private volatile boolean FLAG = true; 38 //队列中存入的数值 39 private AtomicInteger atomicInteger = new AtomicInteger(); 40 //组合一个阻塞队列,通过构造器传入 41 private BlockingQueue blockingQueue; 42 public MyResouce(BlockingQueue blockingQueue) { 43 this.blockingQueue = blockingQueue; 44 } 45 46 //生产者 47 public void product(){ 48 try { 49 while (FLAG){ 50 blockingQueue.put(String.valueOf(atomicInteger.incrementAndGet())); 51 System.out.println("生产者生产第"+blockingQueue.size()+"个产品"); 52 } 53 } catch (InterruptedException e) { 54 e.printStackTrace(); 55 } 56 } 57 58 //消费者 59 public void consumer() throws InterruptedException { 60 while (FLAG){ 61 blockingQueue.take(); 62 System.out.println("消费者消费第"+(blockingQueue.size()+1)+"个产品"); 63 } 64 } 65 66 public void stop(){ 67 FLAG = false; 68 System.out.println("========================"); 69 } 70 }
【2】效果展示: