基於數組的有界阻塞隊列 —— ArrayBlockingQueue

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在閱讀完和 AQS 相關的鎖以及同步輔助器之後,來一起閱讀 JUC 下的和隊列相關的源碼。先從第一個開始:ArrayBlockingQueue。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"公衆號:liuzhihangs,記錄工作學習中的技術、開發及源碼筆記;時不時分享一些生活中的見聞感悟。歡迎大佬來指導!"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"介紹"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由數組支持的有界BlockingQueue阻塞隊列。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個隊列的命令元素FIFO(先入先出)。 隊列的頭是元素一直在隊列中時間最長。 隊列的尾部是該元素已經在隊列中的時間最短。 新元素插入到隊列的尾部,並且隊列檢索操作獲取在隊列的頭部元素。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是一個典型的“有界緩衝區”,在其中一個固定大小的數組保持由生產者插入並受到消費者的提取的元素。 一旦創建,容量不能改變。 試圖put 一個元素到一個滿的隊列將導致操作阻塞; 試圖 take 從空隊列一個元素將類似地阻塞。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此類支持訂購等待生產者和消費者線程可選的公平政策。 默認情況下,這個順序不能保證。 然而,隊列公平設置爲構建 true 保證線程以FIFO的順序進行訪問。 公平性通常會降低吞吐量,但減少了可變性和避免飢餓。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"基本使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\npublic class ArrayBlockingQueueTest {\n\n private static final ArrayBlockingQueue QUEUE = new ArrayBlockingQueue<>(10);\n\n private static final CountDownLatch LATCH = new CountDownLatch(2);\n\n public static void main(String[] args) {\n\n ExecutorService pool = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,\n new LinkedBlockingQueue<>(1024),\n new ThreadFactoryBuilder().setNameFormat(\"Thread-pool-%d\").build(),\n new ThreadPoolExecutor.AbortPolicy());\n\n\n pool.submit(() -> {\n for (int i = 0; i < 100; i++) {\n try {\n Thread.sleep(1000L);\n\n QUEUE.put(\"雞蛋\" + Thread.currentThread().getName());\n System.out.println(\"put 放入元素\");\n } catch (InterruptedException ignored) {\n }\n }\n LATCH.countDown();\n });\n\n\n pool.submit(() -> {\n\n for (int i = 0; i < 100; i++) {\n try {\n Thread.sleep(500L);\n\n String take = QUEUE.take();\n\n System.out.println(\"take = \" + take);\n } catch (InterruptedException ignored) {\n }\n }\n LATCH.countDown();\n\n });\n try {\n LATCH.await();\n } catch (InterruptedException ignored) {\n\n }\n pool.shutdown();\n }\n\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"demo 只是臨時寫的一個,很簡單的版本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"問題疑問"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"ArrayBlockingQueue 的實現原理是什麼?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"入隊列和出隊列方法之間的區別是什麼?"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"源碼分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"基本結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/99/99706eb6e76af457513a2d9c58ad404e.png","alt":"ArrayBlockingQueue-uml-37BHBp","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"參數介紹"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"\n/** 數組 - 存儲隊列中的元素 */\nfinal Object[] items;\n\n/** 下一個 take, poll, peek or remove 的索引 */\nint takeIndex;\n\n/** 下一個 put, offer, or add 的索引 */\nint putIndex;\n\n/** 隊列中的元素數 */\nint count;\n\n\n/** Main lock guarding all access */\nfinal ReentrantLock lock;\n\n/** take 操作時是否等待 */\nprivate final Condition notEmpty;\n\n/** put 操作時是否等待 */\nprivate final Condition notFull;\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"構造函數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public ArrayBlockingQueue(int capacity) {\n this(capacity, false);\n}\n\n// 指定容量,及是否公平\npublic ArrayBlockingQueue(int capacity, boolean fair) {\n if (capacity <= 0)\n throw new IllegalArgumentException();\n this.items = new Object[capacity];\n lock = new ReentrantLock(fair);\n notEmpty = lock.newCondition();\n notFull = lock.newCondition();\n}\n// 初始化的時候放入元素\npublic ArrayBlockingQueue(int capacity, boolean fair,\n Collection extends E> c) {\n this(capacity, fair);\n\n final ReentrantLock lock = this.lock;\n lock.lock(); // Lock only for visibility, not mutual exclusion\n try {\n int i = 0;\n try {\n for (E e : c) {\n checkNotNull(e);\n items[i++] = e;\n }\n } catch (ArrayIndexOutOfBoundsException ex) {\n throw new IllegalArgumentException();\n }\n count = i;\n putIndex = (i == capacity) ? 0 : i;\n } finally {\n lock.unlock();\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"添加元素"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public boolean add(E e) {\n return super.add(e);\n}\n\n// 父類的方法,其實調用的也是 offer\npublic boolean add(E e) {\n if (offer(e))\n return true;\n else\n throw new IllegalStateException(\"Queue full\");\n}\n// 使用鎖\npublic boolean offer(E e) {\n checkNotNull(e);\n // 加鎖\n final ReentrantLock lock = this.lock;\n lock.lock();\n try {\n if (count == items.length)\n return false;\n else {\n enqueue(e);\n return true;\n }\n } finally {\n lock.unlock();\n }\n}\n// 放入元素, 如果隊列滿了,則等待\npublic void put(E e) throws InterruptedException {\n checkNotNull(e);\n final ReentrantLock lock = this.lock;\n lock.lockInterruptibly();\n try {\n while (count == items.length)\n notFull.await();\n enqueue(e);\n } finally {\n lock.unlock();\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"add 方法:調用的是父類 AbstractQueue 的 add 方法,內部調用的是 offer 方法,如果 offer 返回 false,則拋出異常。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"offer 方法:校驗元素非空,加互斥鎖,如果隊列滿了,則返回 false,如果隊列未滿,則調用 enqueue 方法,添加元素。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"put 方法:校驗元素非空,加互斥鎖,如果隊列滿了,則一直自旋等待,隊列未滿則調用 enqueue 方法,添加元素。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以下面還是需要看一下 enqueue 方法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"// 只有在獲取鎖的時候纔可以調用\nprivate void enqueue(E x) {\n // assert lock.getHoldCount() == 1;\n // assert items[putIndex] == null;\n final Object[] items = this.items;\n // putIndex 下一個 put, offer, or add 的索引\n // 對其進行賦值,然後進行 ++putIndex 操作\n items[putIndex] = x;\n // 如果等於長度,則指定爲開始\n if (++putIndex == items.length)\n putIndex = 0;\n // 對元素數進行 ++\n count++;\n // 有元素入隊列,喚醒在等待獲取元素的線程\n notEmpty.signal();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"獲取元素"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public E poll() {\n final ReentrantLock lock = this.lock;\n lock.lock();\n try {\n return (count == 0) ? null : dequeue();\n } finally {\n lock.unlock();\n }\n}\n\npublic E take() throws InterruptedException {\n final ReentrantLock lock = this.lock;\n lock.lockInterruptibly();\n try {\n while (count == 0)\n notEmpty.await();\n return dequeue();\n } finally {\n lock.unlock();\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過源碼可以看出:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"pool 和 take 都是從隊列中獲取元素,二者不同的是,當隊列中沒有元素時,poll 方法返回 null,而 take 方法會一直阻塞等待,直到從隊列中獲取到元素。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"poll 和 take 方法獲取元素都是調用的 dequeue 方法。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private E dequeue() {\n // assert lock.getHoldCount() == 1;\n // assert items[takeIndex] != null;\n final Object[] items = this.items;\n @SuppressWarnings(\"unchecked\")\n // 獲取元素並將元素置爲 null\n E x = (E) items[takeIndex];\n items[takeIndex] = null;\n // takeIndex 下一個 take, poll, peek or remove 的索引\n // 指向下一個元素,並且 元素數減少\n if (++takeIndex == items.length)\n takeIndex = 0;\n count--;\n // 更新迭代器狀態\n if (itrs != null)\n itrs.elementDequeued();\n // 喚醒等待放入元素的線程\n notFull.signal();\n return x;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"查看元素"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public E peek() {\n final ReentrantLock lock = this.lock;\n lock.lock();\n try {\n return itemAt(takeIndex); // null when queue is empty\n } finally {\n lock.unlock();\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Q&A"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Q: ArrayBlockingQueue 的實現原理?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"A:"},{"type":"text","text":" ArrayBlockingQueue 是基於數組實現的,內部使用 ReentrantLock 互斥鎖,防止併發放置元素或者取出元素的衝突問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"Q: 入隊列和出隊列方法之間的區別是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2b/2bc87617c9a3e59eebdc5d915493568b.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"結束語"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ArrayBlockingQueue 中使用了 ReentrantLock 互斥鎖,在元素入隊列和出隊列的時候都進行了加鎖,所以同時只會有一個線程進行入隊列或者出隊列,從而保證線程安全。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章