日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

阻塞隊列

 貪挽懶月 2022-06-20 發(fā)布于廣東

阻塞

隊列

BLOKING

QUEUE

什么是阻塞隊列

阻塞隊列,英文名BlockingQueue,顧名思義,首先它是一個隊列。阻塞就是說在多線程環(huán)境下,線程在某些情況下會被掛起,這就是阻塞,一旦滿足條件,又會被喚醒。那么阻塞隊列的阻塞體現(xiàn)在何處?當一個線程從阻塞隊列中取元素時,如果隊列為空了,那么取元素的操作就會被阻塞,直到有其他線程往隊列中添加了元素;當一個線程往阻塞隊列中添加元素時,如果隊列滿了,那么添加元素的操作也會被阻塞,直到有其他線程從隊列中取走了元素。

為什么要用阻塞隊列

有了阻塞隊列,我們不需要關心何時阻塞線程,何時喚醒線程。因為這些操作阻塞隊列都幫我們做了。隊列為空那么取元素的線程會自動被阻塞,隊列已滿那么添加元素的線程會自動阻塞。

阻塞隊列架構梳理

之前我們只知道 Collection 下面有 set 和 list,其實 queue也是繼承了 Collectionqueue的子類就是 BlockingQueue,不過它還是接口,它總共有九個實現(xiàn)類,下面挑三個最重要的說一說。

  • ArrayBlockingQueue:按照 ArrayList 來理解,由數(shù)組結構組成的有界阻塞隊列。何為有界?其實就是隊列的容量??匆幌滤臉嬙旆椒ǎ?/p>

1public ArrayBlockingQueue(int capacity) {
2        this(capacity, false);
3}

容量調用構造方法時必傳的參數(shù),默認是非公平的,也就是說,它的方法都用Lock上了鎖,我們知道lock可以通過true或者false來指定使用公平鎖還是非公平鎖。所以這里傳的false或者true其實是給lock用的。

  • LinkedBlockingQueue:按照 LinkedList 來理解,由鏈表結構組成的有界阻塞隊列。其實這里有個坑爹的地方,先來看一下它的構造方法:

1public LinkedBlockingQueue() {
2        this(Integer.MAX_VALUE);
3}

它有個無參構造,我們可以不傳容量,默認是 ** int類型的最大值**,這不是坑爹嘛,int類型最大值是21億多,這不就相當于無界嘛。當然,它也有帶參構造,可以指定容量,但是不能指定使用公平還是非公平鎖,默認使用的是非公平鎖。

  • SynchronousQueue:這個隊列比較特殊,只能存儲一個元素。里面有一個元素的時候,添加的線程就會被阻塞。

阻塞隊列核心方法

阻塞隊列的核心方法有四組,如下表:

方法類型拋出異常特殊值阻塞超時
插入add(e)offer(e)put(e)offer(e,time,unit)
移除removepoll()take()poll(time,unit)
檢查element()peek()
拋出異常就是說隊列中沒有元素還去取或者元素滿了還添加,那么就拋出異常。其他幾組也是按照這思路理解,都是顧名思義的,此處不再啰嗦。
阻塞隊列用在哪兒

1、用在哪?
阻塞隊列有哪些應用呢?常見的有以下三個:

  • 生產消費模式;

  • 線程池;

  • 消息中間件

本文將講解生產消費模式中如何使用阻塞隊列。

2、生產消費模式:

  • 多線程編程的口訣
    線程 操縱 資源類,判斷 干活 通知;防止虛假喚醒(判斷一定要用while,不能用if)。

  • 基礎版生產消費模式
    資源類:

 1class Resource{
2    private  Integer num = 0;
3    private Lock lock = new ReentrantLock();
4    private Condition condition = lock.newCondition();
5    public void produce()// 生產的方法
6        lock.lock();
7        try {
8            // 1.判斷
9            while (num != 0){
10                // 等待,不能生產
11                condition.await();
12            }
13            // 2.干活
14            num ++;
15            System.out.println(Thread.currentThread().getName() + "\t" + num);
16            // 3.通知
17            condition.signalAll();
18        }catch (Exception e){
19            e.printStackTrace();
20        }finally {
21            lock.unlock();
22        }
23    }
24
25    public void consum()// 消費的方法
26        lock.lock();
27        try {
28            // 1.判斷
29            while (num == 0){
30                // 等待,不能消費
31                condition.await();
32            }
33            // 2.干活
34            num --;
35            System.out.println(Thread.currentThread().getName() + "\t" + num);
36            // 3.通知
37            condition.signalAll();
38        }catch (Exception e){
39            e.printStackTrace();
40        }finally {
41            lock.unlock();
42        }
43    }
44}

線程操縱資源類:

 1public static void main(String[] args){
2        Resource resource = new Resource();
3        new Thread(() -> {
4            for (int i=1; i<=5; i++){
5                resource.produce();
6            }
7        },"A").start();
8
9        new Thread(() -> {
10            for (int i=1; i<=5; i++){
11                resource.consum();
12            }
13        },"B").start();
14}

這就是基礎版的生產消費模式。準確的說應該是2.0版本,最開始學的是使用synchronized實現(xiàn)的。那么,synchronized和lock到底有什么區(qū)別呢?

  • synchronized 和 lock 的區(qū)別:

原始構成使用方法等待是否可中斷是否公平喚醒
synchronized是關鍵字,屬于JVM層面,底層通過monitor對象來完成不需要用戶手動釋放鎖,鎖住的代碼執(zhí)行完后系統(tǒng)會自動讓線程釋放對鎖的占用不可中斷,除非拋異?;蛘哒_\行完成非公平鎖只能隨機喚醒一個或者喚醒所以線程
ReentrantLock是JUC中的一個類,是API層面的鎖需要手動釋放鎖,若沒釋放,則可能導致死鎖可中斷默認非公平,可設置為公平鎖可以精確喚醒

這里來說一說ReentrantLock的精確喚醒。現(xiàn)有題目如下:

1有A、B、C三個線程,A打印5次,B打印10次,C打印15次,然后又是A打印5次,B打印10次,C打印15次……循環(huán)10輪。

這就是經典的線程按序交替問題。看看如何使用 ReentrantLock 來解決。

 1/** 資源類 */
2class Resource{
3    private   Integer flag = 1// 1: A執(zhí)行,2:B執(zhí)行,3:C執(zhí)行
4    private  Lock lock = new ReentrantLock();
5    public Condition condition1 = lock.newCondition();
6    public Condition condition2 = lock.newCondition();
7    public  Condition condition3 = lock.newCondition();
8
9    public void print(Condition waitCondition, Condition signalCondition, 
10                      Integer num, Integer nowFlag, Integer changedFlag
)
{
11        lock.lock();
12        try { // 1. 判斷
13            while (this.flag != nowFlag){
14                waitCondition.await();
15            }
16            // 2. 干活
17            for (int i=1; i<=num; i++){
18                System.out.println(Thread.currentThread().getName() + "\t" + i);
19            }
20            // 3. 通知
21            this.flag = changedFlag;
22            signalCondition.signal();
23        }catch (Exception e){
24        }finally {
25            lock.unlock();
26        }
27    }
28}

這是資源類,一把鎖,因為有三個線程,需要精確喚醒,就需要三個condition;flag是一個標識,用來判斷是哪個線程進行執(zhí)行;print方法有5個參數(shù),第一個是等待的線程的condition,第二個是需要喚醒的線程的condition,第三個是打印的次數(shù),第四個是當前的flag,第五個是需要修改的flag值。看看線程如何操作這個資源類:

 1public static void main(String[] args){
2        Resource resource = new Resource();
3        new Thread(() -> {
4            for (int i=1; i<=10; i++){
5                resource.print(resource.condition1, resource.condition2, 512);
6                System.out.println("========================================================");
7               }
8           },"A").start();
9
10        new Thread(() -> {
11            for (int i=1; i<=10; i++){
12                resource.print(resource.condition2, resource.condition3, 1023);
13                System.out.println("========================================================");
14            }
15        },"B").start();
16        new Thread(() -> {
17            for (int i=1; i<=10; i++){
18                resource.print(resource.condition3, resource.condition1, 1531);
19                System.out.println("========================================================");
20            }
21        },"C").start();
22}

首先創(chuàng)建A線程,看看A線程調用print方法傳入參數(shù)后是什么情況:

 1lock.lock();
2try { // 1. 判斷
3    while (this.flag != 1){
4        condition1.await();
5    }
6    // 2. 干活
7    for (int i=1; i<=5; i++){
8        System.out.println(Thread.currentThread().getName() + "\t" + i);
9    }
10    // 3. 通知
11    this.flag = 2;
12    condition2.signal();
13}catch (Exception e){ 
14}finally {
15    lock.unlock();
16}

線程A調用時,首先判斷flag是不是1,如果不是,那么線程A就等待,否則就干活,打印5次。干完活要讓線程B執(zhí)行,所以將flag修改為2,然后將線程B喚醒。線程B調用時,自己干完活就喚醒C,線程C干完活就喚醒A……所以執(zhí)行結果就是:

線程按序交替
  • 阻塞隊列版生產消費模式:
    資源類:

 1class Resource {
2    private volatile boolean flag = true// 標識
3    private AtomicInteger atomicInteger = new AtomicInteger();
4    BlockingQueue<Integer> blockingQueue = null;
5
6    public Resource(BlockingQueue<Integer> blockingQueue) {
7        this.blockingQueue = blockingQueue;
8    }
9
10    // 生產的方法
11    public void produce() throws Exception {
12        Integer data = null;
13        boolean result;
14        while (flag) {
15            data = atomicInteger.incrementAndGet();
16            result = blockingQueue.offer(data2L, TimeUnit.SECONDS);
17            if (result)
18                System.out.println(Thread.currentThread().getName() +"成功生產"data +" 號蛋糕!");
19            else
20                System.out.println(Thread.currentThread().getName() + "生產蛋糕失敗!");
21            TimeUnit.SECONDS.sleep(1);
22        }
23        System.out.println(Thread.currentThread().getName() + "停止生產!");
24    }
25
26    // 消費的方法
27    public void consume() throws Exception {
28        System.out.println();
29        Integer data = null;
30       while (flag){
31           data = blockingQueue.poll(2L, TimeUnit.SECONDS);
32           if (data == null){
33               flag = false;
34               System.out.println(Thread.currentThread().getName()+"超過2秒沒取到,停止消費!");
35               return;
36           }
37           System.out.println(Thread.currentThread().getName() +"成功消費" +data" 號蛋糕!");
38       }
39    }
40
41    // 停止的方法
42    public void stop() throws Exception {
43        this.flag = false;
44    }
45}

首先,當flag為true時,進行生產,就是將 atomicInteger 進行自增,放到阻塞隊列中;放完一個就休息1秒鐘。然后是消費的方法,也是當flag為true就進行消費,消費就是從阻塞隊列中取出元素,如果取到的是 null,說明隊列中沒有元素了,就將flaf設為false,退出循環(huán),停止消費。最后的停止的方法,就是將flag設為false,這樣生產和消費都會停止。
操縱資源類:

 1public static void main(String[] args) throws Exception{
2        Resource resource = new Resource(new ArrayBlockingQueue<>(6));
3        new Thread(() -> {
4            System.out.println("生產線程啟動!");
5            try {
6                resource.produce();
7            } catch (Exception e) {
8                e.printStackTrace();
9            }
10
11        }, "生產線程 ").start();
12
13        new Thread(() -> {
14            System.out.println("消費線程啟動!");
15            try {
16                resource.consume();
17            } catch (Exception e) {
18                e.printStackTrace();
19            }
20        }, "消費線程 ").start();
21        // 5秒鐘后停止
22        TimeUnit.SECONDS.sleep(5);
23        System.out.println();
24        System.out.println("5秒鐘后停止生產!");
25        resource.stop();
26}

這里是創(chuàng)建了兩個線程,一個調用生產方法,一個調用消費方法;然后讓主線程睡5秒調用停止的方法。看看運行結果:

阻塞隊列版生產消費模式

這就是阻塞隊列版的生產消費模式,不用我們去控制線程的通信。

    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多