談談阻塞隊列

隊列 + 阻塞隊列

阻塞隊列,顧名思義,首先它是一個隊列,而一個阻塞隊列在數據結構中所起的作用大致如下圖所示:
在這裏插入圖片描述
當阻塞隊列是空時,從隊列中獲取元素的操作將會被阻塞。
當阻塞隊列是滿時,往隊列裏添加元素的操作將會被阻塞。

試圖從空的阻塞隊列中獲取元素的線程將會被阻塞,直到其他的線程往空的隊列插入新的元素。
同樣
試圖往已滿的阻塞隊列中添加新元素的線程同樣也會被阻塞,直到其他的線程從列中移除一個或者多個元素或者完全清空隊列後使隊列重新變得空閒起來並後續新增。

爲什麼用?有什麼好處?

在多線程領域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚醒

爲什麼需要 BlockingQueue
好處是我們不需要關心什麼時候需要阻塞線程,什麼時候需要喚醒線程,因爲這一切 BlockingQueue 都給你一手包辦了

在 concurrent 包發佈以前,在多線程環境下,我們每個程序員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的複雜度。

BlockingQueue 的核心方法

在這裏插入圖片描述

拋出異常 當阻塞隊列滿時,再往隊列裏 add 插入元素會拋 IllegalStateException:Queue full
當阻塞隊列空時,再往隊列裏 remove 移除元素會拋 NoSuchElementException
特殊值 插入方法,成功 true 失敗 false
移除方法,成功返回出隊列的元素,隊列裏面沒有就返回 null
一直阻塞 當阻塞隊列滿時,生產者線程繼續往隊列裏 put 元素,隊列會一直阻塞生產線程直到 put 數據 or 響應中斷推出。
當阻塞隊列空時,消費者線程試圖從隊列裏 take 元素,隊列會一直阻塞消費者線程直到隊列可用。
超時推出 當阻塞隊列滿時,隊列會阻塞生產者線程一定時間,超過限時後生產者線程會退出。

架構梳理 + 種類分析

架構介紹

在這裏插入圖片描述

種類分析

ArrayBlockingQueue:由數組結構組成的有界阻塞隊列。
LinkedBlockingQueue:由鏈表結構組成的有界(但大小默認值爲Integer.MAX_VALUE)阻塞隊列。
PriorityBlockingQueue:支持優先級排序的無界阻塞隊列。
DelayQueue:使用優先級隊列實現的延遲無界阻塞隊列。
SynchronousQueue:不存儲元素的阻塞隊列,也即單個元素的隊列。
理論

SynchronousQueue 沒有容量

與其他 BlockingQueue 不同, SynchronousQueue 是一個不存儲元素的 BlockingQueue。

每一個 put 操作必須要等待一個 take 操作, 否則不能繼續添加元素, 反之亦然。

代碼演示
package com.brian.interview.study.thread;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * Copyright (c) 2020 ZJU All Rights Reserved
 * <p>
 * Project: JavaSomeDemo
 * Package: com.brian.interview.study.thread
 * Version: 1.0
 * <p>
 * Created by Brian on 2020/2/12 21:39
 */

/**
 * 阻塞隊列 SynchronousQueue 演示
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {

        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "\t put 1");
                blockingQueue.put("1");

                System.out.println(Thread.currentThread().getName() + "\t put 2");
                blockingQueue.put("2");

                System.out.println(Thread.currentThread().getName() + "\t put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "AAA").start();

        new Thread(() -> {
            try {
                // 暫時一會兒線程
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());

                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());

                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "BBB").start();
    }
}

LinkedTransferQueue:由鏈表結構組成的無界阻塞隊列。
LinkedBlockingDeque:由鏈表結構組成的雙向阻塞隊列。

用在哪裏

生產者消費者模式

傳統版
package com.brian.interview.study.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Copyright (c) 2020 ZJU All Rights Reserved
 * <p>
 * Project: JavaSomeDemo
 * Package: com.brian.interview.study.thread
 * Version: 1.0
 * <p>
 * Created by Brian on 2020/2/12 22:08
 */

class ShareData {  // 資源類

    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increment() throws Exception {
        lock.lock();
        try {
            // 1、判斷
            while (number != 0) {
                // 等待, 不能幹活
                condition.await();
            }
            // 2、幹活
            number++;
            System.out.println(Thread.currentThread().getName() + "\t" + number);

            // 3、通知喚醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void decrement() throws Exception {
        lock.lock();
        try {
            // 1、判斷
            while (number == 0) {
                // 等待, 不能幹活
                condition.await();
            }
            // 2、幹活
            number--;
            System.out.println(Thread.currentThread().getName() + "\t" + number);

            // 3、通知喚醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

/**
 * 題目:一個初始值爲零的變量, 兩個線程對其交替操作, 一個加1一個減1, 來5輪
 * <p>
 * 1、  線程     操作    資源類
 * 2、  判斷     幹活    通知
 * 3、  防止虛假喚醒機制
 */
public class ProdConsumer_TraditionDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();

        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    shareData.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "AAA").start();

        new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    shareData.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "BBB").start();
    }
}
阻塞隊列版
package com.brian.interview.study.thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Copyright (c) 2020 ZJU All Rights Reserved
 * <p>
 * Project: JavaSomeDemo
 * Package: com.brian.interview.study.thread
 * Version: 1.0
 * <p>
 * Created by Brian on 2020/2/13 12:10
 */

class MyResource {
    private volatile boolean FLAG = true;  // 默認開啓, 進行生產+消費
    private AtomicInteger atomicInteger = new AtomicInteger();

    BlockingQueue<String> blockingQueue = null;

    public MyResource(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
        System.out.println(blockingQueue.getClass().getName());
    }

    public void myProd() throws Exception {
        String data = null;
        boolean retValue;
        while (FLAG) {
            data = atomicInteger.incrementAndGet() + "";
            retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
            if (retValue) {
                System.out.println(Thread.currentThread().getName() + "\t 插入隊列" + data + "成功");
            } else {
                System.out.println(Thread.currentThread().getName() + "\t 插入隊列" + data + "失敗");
            }
            TimeUnit.SECONDS.sleep(1);
        }
        System.out.println(Thread.currentThread().getName() + "\t 大老闆叫停了, 表示 FLAG=false, 生產動作結束");
    }

    public void myConsumer() throws Exception {
        String result = null;
        while (FLAG) {
            result = blockingQueue.poll(2, TimeUnit.SECONDS);
            if (null == result || result.equalsIgnoreCase("")) {
                FLAG = false;
                System.out.println(Thread.currentThread().getName() + "\t 超過2秒鐘沒有取到蛋糕, 消費退出");
                System.out.println();
                System.out.println();
                return;
            }
            System.out.println(Thread.currentThread().getName() + "\t 消費隊列" + result + "成功");
        }
    }

    public void stop() throws Exception {
        this.FLAG = false;
    }
}

/**
 * volatile/CAS/atomicInteger/BlockQueue/線程交互/原子引用
 */
public class ProdConsumer_BlockQueueDemo {
    public static void main(String[] args) throws Exception {
        MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t 生產線程啓動");
            try {
                myResource.myProd();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Product").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t 消費線程啓動");
            try {
                myResource.myConsumer();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Consumer").start();

        // 暫停一會兒線程
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println();
        System.out.println();
        System.out.println();

        System.out.println("5秒鐘時間到, 大老闆main線程叫停, 活動結束");

        myResource.stop();
    }
}

線程池

消息中間件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章