這道題是我親身經歷的一道大廠面試題,很是值得分享!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線程