生产者消费者模式是一种常见的开发模式,在这种模式中,我们需要一个仓库作为缓存,这样能够让生产者和消费者在对资源的生产和消耗上达到一种不浪费资源的目的。
先来看一种错误的写法:
package 消费者生产者模式; import java.util.Date; import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; /** * @program:多线程和IO * @descripton:线程间的通信。 * @author:ZhengCheng * @create:2021/9/17-20:20 **/ public class Demo { public static void main(String[] args) throws InterruptedException { EStorage storage = new EStorage(10); Consumer consumer = new Consumer(storage); Producer producer = new Producer(storage); Thread thread1 = new Thread(consumer); Thread thread2 = new Thread(producer); thread2.start(); thread1.start(); } } class EStorage { public ArrayBlockingQueue abq; private int max = 10; public EStorage( int max) { abq = new ArrayBlockingQueue(max); } } class Producer implements Runnable { private EStorage storage ; public Producer(EStorage storage) { this.storage = storage; } @Override public void run() { synchronized (this){ while (true) { if (storage.abq.remainingCapacity() == 0){ try { System.out.println("生产者停止了生产,等待消费者消费"); Consumer.class.notify(); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.abq.add(new Random().nextInt()); System.out.println(storage.abq.remainingCapacity()); } } } } class Consumer implements Runnable { private EStorage storage ; public Consumer(EStorage stroage) { this.storage = stroage; } @Override public void run() { synchronized (this){ while (true) { if (storage.abq.isEmpty()){ try { System.out.println("消费者停止了消费,等待生产者"); Producer.class.notify(); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.abq.poll(); } } } }
这样的写法,看上去似乎没有什么问题,使用了两把锁。 当仓库空了,就通知生产者生产,自身发生休眠。当仓库满了的时候,就可以通知消费者消费,生产者休眠。乍一看没有什么逻辑上的问题,同时也是很多初学多线程时会有的想法,两个线程互不干扰,同时对一个仓库进行操作。
但是,在程序的运行之中,一定会出现IllegalMonitorStateException。为什么会发生这样的情况呢?首先我们可以从一个情况开始分析,两个线程同时对一个仓库进行操作,由于执行的速度比较快,在两个线程里,如果恰好有一个线程在判断,而另一个线程在进行增加或者减少的操作,那么就会使得判断的值,可能会和仓库目前的值有偏差,产生了脏数据!!!!!
我们可以从多线程的并发角度去理解,就是说,虽然我们有消费者和生产者,但是在一个瞬间,只能有一个人,得到锁,进入仓库进行操作,其他人都得排队!所以我们需要将这把锁,赋予仓库,让仓库来决定,把锁给谁。这样才能保证,在一个瞬间,只会有一个线程在对单例对象进行操作,此时,不会出现脏数据。那多线程还有什么用?反正只能有一个线程在进行操作?多线程的意义在于,可以在多个窗口,对同一个资源进行访问,只要竞争到这把锁,就是可以进行操作的。所以操作的速度足够快,那么尽管只有一把锁,就能够满足许许多多的窗口或者客户端对一个资源进行访问。这就需要仓库的处理速度快,才能做到所谓的多线程高并发。
那么正确的方法应该是怎么样呢?就是把锁交给仓库,让仓库决定,谁能拿到这把锁。
package 消费者生产者模式; import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; /** * @program:多线程和IO * @descripton:手写设计模式 * @author:ZhengCheng * @create:2021/9/19-19:48 **/ public class DemoPractice { public static void main(String[] args) { Mystorage mystorage = new Mystorage(10); Consumer c = new Consumer(mystorage); Producer p= new Producer(mystorage); Thread t1 = new Thread(c); Thread t2 = new Thread(p); t1.start(); t2.start(); } static class Consumer implements Runnable{ Mystorage mystorage; public Consumer(Mystorage mystorage) { this.mystorage = mystorage; } @Override public void run() { while (true){ mystorage.take(); } } } static class Producer implements Runnable{ Mystorage mystorage; public Producer(Mystorage mystorage) { this.mystorage = mystorage; } @Override public void run() { while (true){ mystorage.put(); } } } static class Mystorage{ ArrayBlockingQueue abq; public Mystorage(int maxSize) { abq = new ArrayBlockingQueue(maxSize); } public void put(){ synchronized (this){ if (abq.remainingCapacity() == 0){ System.out.println(Thread.currentThread().getName()+"仓库满了"); try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } abq.add(new Random().nextInt()); System.out.println(Thread.currentThread().getName()+"仓库目前有"+abq.remainingCapacity()+"个物品"); notify(); } } public void take(){ synchronized (this){ if (abq.isEmpty()){ System.out.println(Thread.currentThread().getName()+"仓库空了"); try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } abq.poll(); System.out.println(Thread.currentThread().getName()+"消费者消费了1个物品还剩"+abq.remainingCapacity()); notify(); } } } }