JUC队列-LinkedBlockingQueue(二)
LinkedBlockingQueue介紹
LinkedBlockingQueue是一個以單向鏈表實現的阻塞隊列,鏈表隊列的吞吐量通常高于基于數組的隊列,但是在大多數場景中,其可預見的性能要低。
要注意的是,LinkedBlockingQueue可選容量,防止膨脹,默認大小為Integer.MAX_VALUE。
LinkedBlockingQueue的UML圖:
- head是鏈表的表頭。取出數據時,都是從表頭head處插入。
- last是鏈表的表尾。新增數據時,都是從表尾last處插入。
- count是鏈表的實際大小,即當前鏈表中包含的節點個數。
- capacity是列表的容量,它是在創建鏈表時指定的。
- putLock是插入鎖,takeLock是取出鎖;notEmpty是“非空條件”,notFull是“未滿條件”。通過它們對鏈表進行并發控制。
LinkedBlockingQueue在實現“多線程對競爭資源的互斥訪問”時,對于“插入”和“取出(刪除)”操作分別使用了不同的鎖。對于插入操作,通過“插入鎖putLock”進行同步;對于取出操作,通過“取出鎖takeLock”進行同步。
此外,插入鎖putLock和“非滿條件notFull”相關聯,取出鎖takeLock和“非空條件notEmpty”相關聯。通過notFull和notEmpty更細膩的控制鎖。
LinkedBlockingQueued的源碼分析
構造方法
public LinkedBlockingQueue(int capacity) {if (capacity <= 0) throw new IllegalArgumentException();this.capacity = capacity;last = head = new Node<E>(null); }其他靜態初始化:
// 容量 private final int capacity; // 當前數量 private final AtomicInteger count = new AtomicInteger(0); private transient Node<E> head; // 鏈表的表頭 private transient Node<E> last; // 鏈表的表尾 // 用于控制“刪除元素”的互斥鎖takeLock 和 鎖對應的“非空條件”notEmpty private final ReentrantLock takeLock = new ReentrantLock(); private final Condition notEmpty = takeLock.newCondition(); // 用于控制“添加元素”的互斥鎖putLock 和 鎖對應的“非滿條件”notFull private final ReentrantLock putLock = new ReentrantLock(); private final Condition notFull = putLock.newCondition();添加元素
public void put(E e) throws InterruptedException {if (e == null) throw new NullPointerException();int c = -1;Node<E> node = new Node<E>(e);final ReentrantLock putLock = this.putLock;final AtomicInteger count = this.count;//獲取插入鎖putLock.lockInterruptibly();try {//如果隊列已經滿了則awaitwhile (count.get() == capacity) {notFull.await();}//不滿則插入結點enqueue(node);c = count.getAndIncrement();// 如果在插入元素之后,隊列仍然未滿,則喚醒notFull上的等待線程。if (c + 1 < capacity)notFull.signal();} finally {putLock.unlock();}// 如果在插入節點前,隊列為空;則插入節點后,喚醒notEmpty上的等待線程if (c == 0)signalNotEmpty();}enqueue()方法很簡單:
private void enqueue(Node<E> node) {// assert putLock.isHeldByCurrentThread();// assert last.next == null;last = last.next = node; }取出元素
public E take() throws InterruptedException {E x;int c = -1;final AtomicInteger count = this.count;final ReentrantLock takeLock = this.takeLock;// 獲取“取出鎖”,若當前線程是中斷狀態,則拋出InterruptedException異常takeLock.lockInterruptibly();try {// 若“隊列為空”,則一直等待。while (count.get() == 0) {notEmpty.await();}// 取出元素x = dequeue();// 取出元素之后,將“節點數量”-1;并返回“原始的節點數量”。c = count.getAndDecrement();if (c > 1)notEmpty.signal();} finally {// 釋放“取出鎖”takeLock.unlock();}// 如果在“取出元素之前”,隊列是滿的;則在取出元素之后,喚醒notFull上的等待線程。if (c == capacity)signalNotFull();return x; } private E dequeue() {// assert takeLock.isHeldByCurrentThread();// assert head.item == null;Node<E> h = head;Node<E> first = h.next;h.next = h; // help GChead = first;E x = first.item;first.item = null;return x; }總結:
ArrayBlockingQueue和LinkedBlockingQueue有很多相似的地方,他們的不同點在于:
他們都是阻塞隊列,但ArrayBlockingQueue是數組實現的,并且是有界限的;而LinkedBlockingQueue是鏈表實現的,是無界限的,而且ArrayBlockingQueue只用了一個獨占鎖,而LinkedBlockingQueue用了兩個獨占鎖,所以ArrayBlockingQueue沒有實現讀寫分離,而LinkedBlockingQueue可以。
為什么要這么設計呢?
LinkedBlockingQueue的較大一部分時間需要構造節點,導致較長的等待,所以同時存取有較大優化,而ArrayBlockingQueue的不用構造節點,加鎖和解鎖的時間可能占比較大。
總結
以上是生活随笔為你收集整理的JUC队列-LinkedBlockingQueue(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JUC队列-ArrayBlockingQ
- 下一篇: JUC队列-LinkedBlocking