三种方式实现生产者消费者模式:synchronized+wait+notify,lock+condition,LinkedBlockingQueue

synchronized+wait+notify,方法比较低级

关键在于:put和get方法必须是synchronized修饰的,并且容器为空的时候不能get,容器满了的时候,不能put。

public class Container<T> {
    List<T> list=new LinkedList<>();
    int max=10;
    synchronized void put(T t){
        while(list.size()==max){
            try {
                this.wait();
            }catch(InterruptedException e){
                System.out.println("InterruptedException!!");
            }
        }
        list.add(t);
        System.out.println("add object"+t.hashCode());
        this.notifyAll();//通知所有等待者进入锁池争夺线程,即通知消费者进行消费
    }
    synchronized void get(){
        while(list.size()==0){
            try{
                this.wait();//wait 99%的情况都是和while一起用。防止只判断一次,再获得锁的时候不继续判断就直接执行
            }catch(InterruptedException e){
                System.out.println("InterruptedException!!");
            }
        }
        T ans=list.remove(0);
        System.out.println("Get this Object:"+ans.hashCode());
        this.notifyAll();//通知生产者继续生产
        //这里用notifyAll而不是notify的意义是:notify只能令一个处于等待池中的线程去锁池中争抢锁
        //而notifyAll是让等待池的所有的线程都去锁池争抢资源
        //如果用notify的话,很可能只选了一个同类的线程,也只能在while循环里一直等待,程序就不能正常运行
        //effective java中说的:使用notifyAll,不要使用notify
    }

    public static void main(String[] args) {
        Container<Integer> m=new Container<>();
        //开启10个消费者线程
        for(int i=0;i<30;i++){
            new Thread(m::get).start();
        }
        //开启30个生产者线程
        //所有的线程只有获取到m的锁,才能执行。
        for (int c=0;c<30;c++){
            int finalC = c;
            new Thread(()->{
                m.put(finalC);
            }).start();
        }
    }
}

这里思考一个问题:notify和notifyAll的区别
上述代码中用notifyAll而不是notify的意义是:
notify只能令一个处于等待池中的线程去锁池中争抢锁,而notifyAll是让等待池的所有的线程都去锁池争抢资源;如果用notify的话,很可能只选了一个同类的线程,也只能在while循环里一直等待,程序就不能正常运行。

lock+condition实现生产者消费者模式,稍微高级一点

当容器为空时,不能get,说明消费者线程需要阻塞。
容器满了的时候不能put,说明生产者线程需要阻塞。
当put对象之后,可以唤醒消费者线程;当get对象之后,可以唤醒生产者线程。
创建两种条件,一个是生产者条件,一个是消费者条件,很有效。

public class Container2<T> {
    List<T> list=new LinkedList<>();
    int max=10;
    ReentrantLock lock=new ReentrantLock();
    Condition producer=lock.newCondition();//使用lock new出不同的condition,调用await和signal函数准确的控制线程
    Condition consumer=lock.newCondition();//也不是任何时候都是用Reentrantlock,比较简单的用synchronized能实现的,也可以用synchronized和wait来实现
                                            //抢购的时候用sychronized和reentrantlock都差不多,最好的是用原子性的一些方法。
    void put(T t) {
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"得到锁");
            while(list.size()==max){
                producer.await();//如果已经放满了,就会让当前线程阻塞,释放锁
            }
            System.out.println(Thread.currentThread().getName()+"得到锁");
            list.add(t);
            System.out.println("addObject:" +t.hashCode());
            //把所有处于等待队列中的线程转移到SYNC 队列,让其尝试获取锁
            consumer.signalAll();
        }catch(InterruptedException e){
            System.out.println("InterruptedException!!");
        }finally {
            lock.unlock();
        }

    }
    void get(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"得到锁");
            while (list.size() == 0) {
                consumer.await();
                //如果没有可以获取的对象,就在while循环里阻塞着
                //当interrupted之后,如果没有拿到锁,继续阻塞REINTERRUPT
                //只有拿到锁才能判断list.size()并向下执行
            }
            System.out.println(Thread.currentThread().getName()+"得到锁");
            T ans=list.remove(0);
            System.out.println("get Object:"+ans.hashCode());
            producer.signalAll();
        }catch(InterruptedException e){
            System.out.println("InterruptedException!!");
        }finally{
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Container2<Integer> c=new Container2<>();
        for(int i=0;i<30;i++){
            new Thread(c::get,"消费者"+i).start();
        }
        try{
            TimeUnit.SECONDS.sleep(2);
        }catch(InterruptedException e){
            System.out.println("InterruptedException!!");
        }
        for(int j=0;j<30;j++){
            int finalJ=j;
            new Thread(()->{
                c.put(finalJ);
            },"生产者"+finalJ).start();
        }
    }
}
LinkedBlockingQueue实现生产者消费者模式

LinkedBlockQueue是单端的自动阻塞无限队列。
LinkedBlockDeque是双端的自动阻塞无限队列。
添加元素,有add、offer和put方法,只有put方法在容器满时会阻塞。底层是调用await方法,自动阻塞。
取出元素,有pool、peek、get、take方法,只有take方法在容器空时,会自动阻塞,底层也是调用await方法。
但是要注意takeFirst和putLast方法是lock原子的,但是如果和其他的代码一起执行比如System.out.println等,是不能保持原子性的。

public class test {
    static final LinkedBlockingDeque<Integer> que=new LinkedBlockingDeque<>();
    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            new Thread(()->{
                try{
                    int ans=que.takeFirst();
                }catch(InterruptedException e){
                    System.out.println("InterruptedException!!");
                }
            }).start();
        }
        try{
            TimeUnit.SECONDS.sleep(5);
        }catch(InterruptedException e){
            System.out.println("InterruptedException!!");
        }
        for(int j=0;j<10;j++){
            int finalJ1 = j;
            new Thread(()->{
                try{
                    que.putLast(finalJ1);
                }catch(InterruptedException e){
                    System.out.println("InterruptedException!!");
                }
            }).start();
        }

    }
}

关于await方法如何实现阻塞的,可以看我的另一篇文章:
从源码分析await和signal怎样实现线程间通信

原创文章 64 获赞 27 访问量 9420

猜你喜欢

转载自blog.csdn.net/weixin_44893585/article/details/104581645