多線程(二)阻塞隊列

先分享個有趣的故事給大家,是女大學生被男老師追求的事情,故事的結局不是你們想的那種老牛喫嫩草,喫不到就用強的那種,男的40,女的19,我個人感覺男的比較虛僞的認真了,這是我的個人看法,也可以說他愛的太深了,這個反正我是不太信,女的有點優柔寡斷,明明不喜歡說出來就好,以怕掛科爲理由,一直和老師瞎扯正義和大道理,現在的老師還不至於無緣無故就掛你科。故事到這了,不說了,反正戀愛的請珍惜,分手的祝你分手快樂祝你找個更好的,單身的對自己好點(愛護自己的雙手)。


目錄

  1. 常用阻塞場景
  2. 定義
  3. 核心方法
  4. java中阻塞隊列種類
  5. 實現原理(源碼查看)
  6. 使用場景
  7. 總結

1.常用的阻塞場景
  1. 隊列中的生產者和消費者
    生產者:是往隊列裏添加元素的線程
    消費者:是從隊列裏拿元素的線程
    阻塞隊列就是容器,存放元素(別矇蔽好吧,元素就是object)
  2. 阻塞場景
    1)當隊列中沒有數據的情況下,消費者端的所有線程被掛起(也就是阻塞了),直到有數據存放入隊列
    2)當隊列中填滿數據的情況下,生產者端的所有線程被掛起,直到隊列中有空的位置,線程被自動喚醒
2.定義

符合1.2中兩種場景的隊列,稱爲阻塞隊列

3.核心方法

放入數據的方法:

  • offer(anObject):表示將anObject放入到隊列BlockingQueue中,BlockingQueue中可以容納就返回true,否則返回false。(本方法是不會阻塞當前執行方法的線程,反回false,有了解嗎?)
  • offer(E o,long timeout,TimeUnit unit):可以設定等待時間。如果在指定時間內還不能往隊列中加入BlockingQuere,則返回false。
  • put(anObjiect):和offer是一樣的,但是失敗它不會返回false,會直接阻塞當前方法的線程。

獲取數據的方法:

  • poll(long timeout,TimeUnit unit):從BlockingQueue中取出隊首的對象。在指定時間內,隊列一旦有數據可取,則立即返回隊列中的數據,否則超過時間就返回失敗。
  • take():取走BlockingQueue裏排在首位的對象。若BlockingQueue爲空,則阻斷進入等待狀態,直到BlockingQueue有新的數據被加入。
  • drainTo():一次性從BlockingQueue獲取所有可用的數據對象(還可以指定獲取數據的個數)。通過該方法,可以提升獲取數據的效率;無需多次分批加鎖或者釋放鎖。

當然不單單就這6個方法,你可以點看一下,我就不一一介紹了。
在這裏插入圖片描述

4.java中阻塞隊列種類
  • ArrayBlockingQueue:由數組結構組成的有界阻塞隊列
  • LinkedBlockingQueue:由鏈表結構組成的有界組賽隊列
  • PriorityBlockingQueue:支持優先級排序的無界組賽隊列
  • DelayQueue:使用優先級排序的無界阻塞隊列
  • SynchronousQueue:不存儲元素的組賽隊列
  • LinkedTransferQueue:由鏈表結構組成的無界組賽隊列
  • LinkedBlockingDeque:由鏈表結構組成的雙向阻塞隊列

特點:
1.通常情況下,用前兩種就足夠了。

2.PriorityBllockingQueue不能保證同優先級元素的順序。

3.DelayQueue指定元素到期的時間,只有在元素到期時才能從隊列中取走。

4.SynchronousQueue嚴格來說它並不是容器。由於隊列沒有容量,因此不能調用peek操作(返回隊列頭元素)

5.LinkedTransferQueue實現了一個重要接口TransferQueue該接口有5個方法,其中有3個比較重要的方法,一個是transfer(E e)如果沒有消費者等待接受數據,會把元素插入到隊列尾部,隊列進入阻塞狀態,直到有消費者取走該元素,tryTransfer(E e)看名字就知道不會阻塞線程,當然也可以給它傳入時間,在指定的時間內元素未被消費者獲取則返回false。

6.LinkedBlockingDeque是雙向的隊列,多線程同時入隊時減少了一半的競爭。其中的First單詞結尾的方法表示插入、獲取或者移除雙端隊列的第一個元素;以Last單純結尾的方法,表示插入、獲取或者移除雙端隊列的最後一個元素。

5.實現原理(源碼查看)

老規矩,打開我們的Source Insight 4.0,我以ArrayBlockingQueue爲例,搜索看一下這個類定義了那些變量
在這裏插入圖片描述
我們維護的是一個Object的數組,takeIndex和putIndex分別表示隊首元素和隊尾元素的下標,count表示隊列元素的個數,lock則是一個可重入鎖,notEmpty和notFull是等待條件。在看看關鍵方法put
在這裏插入圖片描述
可以看出,先獲取鎖,在判斷當前元素個數是否等於數組長度,如果相等,則調用notFull.await()進行等待。當此線程被其他線程喚醒時,通過enqueue(e)方法插入元素,最後解鎖。在看看enqueue(e)方法
在這裏插入圖片描述
插入成功後,就通過notEmpty.signal()喚醒正在等待取元素的線程。再來看看take方法。
在這裏插入圖片描述
這個應該差不多懂了吧,和put方法類似,如果可以取,就通過dequeue方法取得元素,我們在看看dequeue方法得源碼。
在這裏插入圖片描述
和enqueue方法類似,在獲取元素後,通過等待條件notFull的signal方法喚醒正在等待插入元素的線程。

6.使用場景

第一種使用Object.wait()、Object.notify()和非阻塞隊列實現生產者-消費者模式

package com.example.syt.myapplication;

import java.util.PriorityQueue;

/**
 * Created by syt on 2019/4/15.
 */

public class Test {
    private int queueSize=10;
    private PriorityQueue<Integer> queue=new PriorityQueue<Integer>(queueSize);

    public static void main(String[] args) {
        Test test=new Test();
        Producer producer=test.new Producer();
        Consumer consumer=test.new Consumer();
        producer.start();
        consumer.start();
    }
    class Consumer extends Thread{
        @Override
        public void run() {
            while (true){
                synchronized (queue){
                    while (queue.size()==0){
                        try{
                            System.out.println("隊列空,等待數據");
                            queue.wait();
                        }catch (InterruptedException e){
                            e.printStackTrace();
                            queue.notify();
                        }
                    }
                    //每次移走隊首元素
                    queue.poll();
                    queue.notify();
                }
            }
        }
    }
    class Producer extends Thread{
        @Override
        public void run() {
            while (true){
                synchronized (queue){
                    while (queue.size()==queueSize){
                        System.out.println("隊列滿,等待有餘空間");
                        try {
                            queue.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            queue.notify();
                        }
                    }
                    //每次插入一個元素
                    queue.offer(1);
                    queue.notify();
                }
            }
        }
    }
}

在看看我們使用阻塞隊列去實現生產者-消費者模式

package com.example.syt.myapplication;

import java.util.concurrent.LinkedBlockingDeque;

/**
 * Created by syt on 2019/4/15.
 */

public class Test {
    private int queueSize=10;
    private LinkedBlockingDeque<Integer> queue=new LinkedBlockingDeque<Integer>(queueSize);

    public static void main(String[] args) {
        Test test=new Test();
        Producer producer=test.new Producer();
        Consumer consumer=test.new Consumer();
        producer.start();
        consumer.start();

    }
    class Consumer extends Thread{
        @Override
        public void run() {
            while (true){
                try {
                    queue.take();
                    System.out.println("取隊首");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    class Producer extends Thread{
        @Override
        public void run() {
            try {
                queue.put(1);
                System.out.println("往隊列加一個");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

總結

阻塞隊列的特點:隊空時消費阻塞,隊滿時生產阻塞。多線程消費數據起到了加速消費的作用,使得生產的數據不會在隊裏積壓過多,而生產的數據也不會丟失處理了。

奇淫巧計,爲求一讚,如有疑問或者博主寫的不足的地方請留言或者聯繫QQ2714730493
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章