天天看點

面試官:你手寫過堵塞隊列嗎?

面試官:你好,你先做個自我介紹吧

某人:面試官你好,我叫開局一張嘴面試全靠吹,某某年畢業,畢業自家裡蹲大學,做過某某項目。。。。。。

面試官微微一笑,捋了捋稀疏的頭發:看你履歷,你精通多線程?那你手寫過堵塞隊列嗎?

某人心裡出現一萬個問号,堵塞隊列是啥玩意?平時基本都是crud,頂多用多線程跑資料

面試官:你手寫過堵塞隊列嗎?

某人:沒有手寫過。

面試官:哦,那你說下堵塞隊列吧

某人支支吾吾:這個有點忘了

面試官:沒事,那我們下一個。

此處省略一萬字。

面試官扭了扭嚴重負荷的頸椎:先到這裡吧,你先回去等通知。

某人:好的。

不出意外,某人等了一個月,等的望眼欲穿,也沒等到那個期待的電話。

1.什麼是隊列

隊列是一種特殊的線性表,特殊之處在于它隻允許在表的前端(front)進行删除操作,而在表的後端(rear)進行插入操作,和棧一樣,隊列是一種操作受限制的線性表。進行插入操作的端稱為隊尾,進行删除操作的端稱為隊頭。
面試官:你手寫過堵塞隊列嗎?

隊列其實就是跟平時排隊一樣,按照順序來,先排隊的先買到東西,後排隊的後買到東西,排隊的第一個叫隊頭,最後一個叫隊尾,這就是隊列的先進先出,這是和棧最大的差別。

2.什麼是堵塞隊列?

面試官:你手寫過堵塞隊列嗎?

在這裡插入圖檔描述

當隊列為空時,消費者挂起,隊列已滿時,生産者挂起,這就是生産-消費者模型,堵塞其實就是将線程挂起。

因為生産者的生産速度和消費者的消費速度之間的不比對,就可以通過堵塞隊列讓速度快的暫時堵塞,

如生産者每秒生産兩個資料,而消費者每秒消費一個資料,當隊列已滿時,生産者就會堵塞(挂起),等待消費者消費後,再進行喚醒。

堵塞隊列會通過挂起的方式來實作生産者和消費者之間的平衡,這是和普通隊列最大的差別。

3.如何實作堵塞隊列?

jdk其實已經幫我們提供了實作方案,java5增加了concurrent包,concurrent包中的BlockingQueue就是堵塞隊列,我們不需要關心BlockingQueue如何實作堵塞,一切都幫我們封裝好了,隻需要做一個沒有感情的API調用者就行。往期面試題:001期~180期彙總

4.BlockingQueue如何使用?

BlockingQueue本身隻是一個接口,規定了堵塞隊列的方法,主要依靠幾個實作類實作。

4.1BlockingQueue主要方法

面試官:你手寫過堵塞隊列嗎?

1.插入資料

(1)offer(E e):如果隊列沒滿,傳回true,如果隊列已滿,傳回false(不堵塞)

(2)offer(E e, long timeout, TimeUnit unit):可以設定等待時間,如果隊列已滿,則進行等待。超過等待時間,則傳回false

(3)put(E e):無傳回值,一直等待,直至隊列空出位置

2.擷取資料

(1)poll():如果有資料,出隊,如果沒有資料,傳回null

(2)poll(long timeout, TimeUnit unit):可以設定等待時間,如果沒有資料,則等待,超過等待時間,則傳回null

(3)take():如果有資料,出隊。如果沒有資料,一直等待(堵塞)

4.2BlockingQueue主要實作類

1.ArrayBlockingQueue:ArrayBlockingQueue是基于數組實作的,通過初始化時設定數組長度,是一個有界隊列,而且ArrayBlockingQueue和LinkedBlockingQueue不同的是,ArrayBlockingQueue隻有一個鎖對象,而LinkedBlockingQueue是兩個鎖對象,一個鎖對象會造成要麼是生産者獲得鎖,要麼是消費者獲得鎖,兩者競争鎖,無法并行。

2.LinkedBlockingQueue:LinkedBlockingQueue是基于連結清單實作的,和ArrayBlockingQueue不同的是,大小可以初始化設定,如果不設定,預設設定大小為Integer.MAX_VALUE,LinkedBlockingQueue有兩個鎖對象,可以并行處理。

3.DelayQueue:DelayQueue是基于優先級的一個無界隊列,隊列元素必須實作Delayed接口,支援延遲擷取,元素按照時間排序,隻有元素到期後,消費者才能從隊列中取出。

4.PriorityBlockingQueue:PriorityBlockingQueue是基于優先級的一個無界隊列,底層是基于數組存儲元素的,元素按照優選級順序存儲,優先級是通過Comparable的compareTo方法來實作的(自然排序),和其他堵塞隊列不同的是,其隻會堵塞消費者,不會堵塞生産者,數組會不斷擴容,這就是一個彩蛋,使用時要謹慎。

5.SynchronousQueue:SynchronousQueue是一個特殊的隊列,其内部是沒有容器的,是以生産者生産一個資料,就堵塞了,必須等消費者消費後,生産者才能再次生産,稱其為隊列有點不合适,現實生活中,多個人才能稱為隊,一個人稱為隊有些說不過去。

5.手寫堵塞隊列

/**
 * @author yz
 * @version 1.0
 * @date 2020/10/31 11:24
 */
public class YzBlockingQuery {

    private Object[] tab; //隊列容器

    private int takeIndex; //出隊下标

    private int putIndex; //入隊下标

    private int size;//元素數量

    private ReentrantLock reentrantLock = new ReentrantLock();

    private Condition notEmpty;//讀條件

    private Condition notFull;//寫條件

    public YzBlockingQuery(int tabCount) {
        if (tabCount <= 0) {
            new NullPointerException();
        }

        tab = new Object[tabCount];
        notEmpty = reentrantLock.newCondition();
        notFull = reentrantLock.newCondition();
    }

    public boolean offer(Object obj) {
        if (obj == null) { throw new NullPointerException(); }
        try {
            //擷取鎖
            reentrantLock.lock();
            //隊列已滿
            while (size==tab.length){
                System.out.println("隊列已滿");
                //堵塞
                notFull.await();
            }
            tab[putIndex]=obj;
            if(++putIndex==tab.length){
                putIndex=0;
            }
            size++;
            //喚醒讀線程
            notEmpty.signal();
            return true;
        } catch (Exception e) {
            //喚醒讀線程
            notEmpty.signal();
        } finally {
            reentrantLock.unlock();
        }
        return false;
    }


    public Object take(){
        try {
            reentrantLock.lock();
            while (size==0){
                System.out.println("隊列空了");
                //堵塞
                notEmpty.await();
            }
            Object obj= tab[takeIndex];
            //如果到了最後一個,則從頭開始
            if(++takeIndex==tab.length){
                takeIndex=0;
            }
            size--;
            //喚醒寫線程
            notFull.signal();
            return obj;
        }catch (Exception e){
            //喚醒寫線程
            notFull.signal();
        }finally {
            reentrantLock.unlock();
        }
        return null;
    }


    public static void main(String[] args) {
        Random random = new Random(100);
        YzBlockingQuery yzBlockingQuery=new YzBlockingQuery(5);
        Thread thread1 = new Thread(() -> {
            for (int i=0;i<100;i++) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                yzBlockingQuery.offer(i);
                System.out.println("生産者生産了:"+i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i=0;i<100;i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Object take = yzBlockingQuery.take();
                System.out.println("消費者消費了:"+take);
            }
        });

        thread1.start();
        thread2.start();
    }
}      

繼續閱讀