BAT面試題:使用數組實現一個簡單的阻塞隊列

這道題是我親身經歷的一道大廠面試題,很是值得分享!java

這道題能夠分爲兩個步驟進行編碼解答,第一步是基於數組實現一個隊列,第二步是實現線程阻塞。面試

若是是基於數組實現棧的數據結構,那麼咱們只須要一個指針進行來回移動便可。數組

想象一下,腦海中有一個豎立起來的棧,指針上移表明元素進棧,指針下移,表明元素出棧,整個過程只須要一個指針進行上下移動便可。數據結構

由此能夠寫出下面的代碼:ide

import java.util.Arrays;
import java.util.EmptyStackException;

public class ArrayStack<T> {
    private Object[] elements = new Object[16]; //數組大小默認16
    private int count; //1.-1後指向棧內末尾的元素 2.統計棧內元素的數量

    public void push(T e){
        //數組擴容
        if (count == elements.length){
            elements = Arrays.copyOf(elements,2*count+1);
        }
        elements[count++] = e;
    }

    public T pop(){
        if (count == 0){
            throw new EmptyStackException();
        }
        T o = (T) elements[--count];
        elements[count] = null; //防止內存泄漏
        return o;
    }

    public static void main(String[] args) {
        ArrayStack<Integer> arrayStack = new ArrayStack<>();
        arrayStack.push(1);
        arrayStack.push(2);
        System.out.println(arrayStack.pop()); //2
        System.out.println(arrayStack.pop()); //1
    }

}

可是,基於數組實現隊列卻須要兩個指針進行來回移動。編碼

想象一下,腦海中有一個橫放的空隊列,在向隊列進行ADD操做時,ADD指針從首端右移,直到移到末端填滿隊列;在向一個滿隊列進行GET操做時,GET指針從首端右移,直到移到末端取出全部元素。線程

這些步驟都是須要前提條件的,滿隊列沒法進行ADD操做,同理,空隊列沒法進行GET操做,在ADD和GET操做以前還須要對此進行檢查。指針

其次,ADD和GET指針會一直循環移動下去,它們移動到末端並不表明任何意義(即ADD指針移動到末端不表明隊列已滿,GET指針移動到末端不表明隊列已空),因此,咱們須要一個變量用作計數器,專門負責統計隊列元素數量,檢查隊列是否已滿或已空。code

至於阻塞/喚醒部分的邏輯就比較簡單了,只須要使GET線程訪問空隊列時進行阻塞,ADD線程訪問滿隊列時進行阻塞便可,並在GET方法、ADD方法操做結束時喚醒一下對方線程,若是對方正在阻塞狀態就能夠被喚醒繼續向下運行。對象

由此能夠寫出下面的代碼:

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

public class ArrayBlockingQueue<T> {
    private Lock lock = new ReentrantLock();
    private Object[] item;
    //兩個指針負責ADD與GET操做
    //count負責統計元素數量
    private int addIndex, getIndex, count;
    //等待、通知
    private Condition addCondition = lock.newCondition();
    private Condition getCondition = lock.newCondition();

    public ArrayBlockingQueue(int size) {
        item = new Object[size];
    }

    public void add(T t) {
        lock.lock();
        try {
            System.out.println("正在ADD對象:" + t);
            while (count == item.length) {
                try {
                    System.out.println("隊列已滿,阻塞ADD線程");
                    addCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //隊列未滿,添加對象並使計數器+1
            item[addIndex++] = t;
            count++;
            //ADD指針指向末端,重置
            if (addIndex == item.length) {
                addIndex = 0;
            }
            System.out.println("喚醒GET線程");
            getCondition.signal();
        } finally {
            lock.unlock();
        }
    }

    public T get() {
        lock.lock();
        try {
            while (count == 0) {
                try {
                    System.out.println("隊列空了,阻塞GET線程");
                    getCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //隊列不空,獲取對象並使計數器-1
            T t = (T) item[getIndex++];
            System.out.println("正在GET對象:" + t);
            count--;
            //GET指針到達末端、重置
            if (getIndex == item.length) {
                getIndex = 0;
            }
            System.out.println("喚醒ADD線程");
            addCondition.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3);
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    queue.add(i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    queue.get();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

    }
}
//  打印輸出:
//        正在ADD對象:0
//        喚醒GET線程
//        正在GET對象:0
//        喚醒ADD線程
//        隊列空了,阻塞GET線程
//        正在ADD對象:1
//        喚醒GET線程
//        正在GET對象:1
//        喚醒ADD線程
//        隊列空了,阻塞GET線程
//        正在ADD對象:2
//        喚醒GET線程
//        正在GET對象:2
//        喚醒ADD線程
相關文章
相關標籤/搜索