多线程--阻塞队列and生产消费者模型

一.阻塞队列是什么

阻塞队列是一种特殊的数列,也遵循先进先出的原则

二.阻塞队列的特性:

1.阻塞队列是一种线程安全的数据结构

2.阻塞特性:

a)队列为空,尝试出队列,出队列操作就会阻塞,阻塞到其他线程添加元素为止

b)队列为满,尝试入队列,入队列操作也会阻塞,阻塞到其他线程取走元素为止

下面是Java中阻塞队列接口 BlockingDeque 由其LinkedBlockingDeque类实现(双端队列)

也可以由普通阻塞队列实现BlockingQueue 由ArrayBlockingQueue 和 LinkedBlockingQueue,PriorityBlockingQueue, TransferQueue实现

下面是用的双端队列

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        BlockingDeque queue = new LinkedBlockingDeque<>();//阻塞队列(天然线程安全)

        queue.put("abcd");//放元素put 也可以用普通队列的offer 但是没有阻塞功能
        //这里的阻塞是队列满的时候就在这里阻塞

        String elem = queue.take();//出元素take 也可以用poll 也是没有阻塞功能
        //这里的阻塞是队列为空的时候阻塞

        System.out.println(elem);
    }
}

阻塞演示 

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        BlockingDeque queue = new LinkedBlockingDeque<>(100);

        for (int i = 0; i < 100; i++) {
            queue.put("abc");
        }
        System.out.println("队列已经满了");

        queue.put("666");//这里就会阻塞可以通过jcosole查看线程的状态(跟踪线程)
        System.out.println("再次尝试put元素");
    }
}

 

阻塞队列一个典型的应用场景就是生产消费者模型

三.生产消费者模型

生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取. ​

这样就可以达到生产者和消费者的解耦合

多线程--阻塞队列and生产消费者模型_第1张图片

 

解耦合的目的是为了让后续的修改成本低队列一般不会修改,修改A的代码也就只用考虑修改A就行不用考虑B的代码情况B也是跟队列耦合的 

阻塞队列太重要了,公司中甚至会部署成一个单独的服务器,这个队列也称为“消息队列”

上述是生产消费者模型的优势之一:解耦合

还有一个重要的优势:削峰填谷

阻塞队列相当于一个缓冲区,平衡了生产者和消费者的处理能力

下面是生产消费者模型的案例↓

public class Demo {
    public static void main(String[] args) {
        //至少一个生产者线程,一个消费者线程

        BlockingDeque queue = new LinkedBlockingDeque<>(100);//阻塞队列大小为100

        Thread producer = new Thread(() -> {//生产者线程
            int n = 0;
            while (true) {
                try {
                    queue.put(n);
                    System.out.println("生产元素 " + n);
                    n++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }, "producer");

        Thread consumer = new Thread(() -> {//消费者线程
            try {
                while (true) {
                    Integer n = queue.take();
                    System.out.println("消费元素 " + n);
                    Thread.sleep(1000);//这里也是限制
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "consumer");

        producer.start();
        consumer.start();
    }
}
 * 以上生产者和消费者之间速度差不多 能达到一种平衡 基本不会阻塞 效率很高
 * 这里阻塞是为了突发情况 保证安全 (防止信息量暴增) 生产消费者模型就是在尽量不阻塞(平衡生产者消费者之间的效率)的情况下达到最高效率

下面自己实现阻塞队列(单端队列)

class MyBlockingQueue {

    private String[] data = null;

    //队列首
    private int head = 0;

    //队列尾
    private int tail = 0;

    //元素个数
    private int size = 0;

    public MyBlockingQueue (int capacity) {
        data = new String[capacity];
    }

    public void put(String elem) throws InterruptedException {
        synchronized (this) {
            while (size >= data.length) {
                //队列满了 阻塞
                this.wait();//wait之前判断一次 唤醒也判定一次
            }
            data[tail] = elem;
            tail++;
            if (tail >= data.length) {
                tail = 0;
            }
            //tail = (tail + 1) % data.length; //与上面四行代码逻辑相同
            size++;
            this.notify();//唤醒take的wait(size++队列不为空)
        }
    }

    public String take() throws InterruptedException {
        synchronized (this) {
            /**
             * 如果只是一个线程take 一个线程put不会出现不会出现自己唤醒自己的情况
             * 多个线程take 多个线程put 确实有自己唤醒自己的风险 但是用过while循环条件避免这样的影响
             * while也能在interrupt中断的情况下还能继续让其等待 不会出现size = - 1的情况
             */
            while (size == 0){
                //队列空了 需要阻塞
                this.wait();//wait之前判断一次 唤醒也判定一次
            }
            String ret = data[head];
            head++;
            if (head >= data.length) {
                head = 0;
            }
            //head = (head + 1) % data.length;
            size--;
            this.notify();//唤醒put的wait(size--队列就不满了)
            return ret;
        }
    }
    /**
     * 比如有若干个线程用这个队列
     * 要么所有阻塞在put 要么 take
     * 不可能出现一些阻塞在put 一些阻塞在take (因为队列不可能是既满又空)
     */
}

利用自己实现的阻塞队列来完成生产消费者模型

public class Demo {
    public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue(100);

        Thread producer = new Thread(() -> {
            int n =0;
            while (true) {
                try {
                    queue.put(n + "");
                    System.out.println("生产元素:" + n);
                    //Thread.sleep(1000);
                    n++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });

        Thread consumer = new Thread(() -> {
           while (true) {
               try {
                   String n = queue.take();
                   System.out.println("消费元素:" + n);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });

        producer.start();
        consumer.start();

    }
}

 

你可能感兴趣的:(java,开发语言,intellij-idea,中间件,安全,jvm,java-ee)