亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

詳細分析Java并發集合LinkedBlockingQueue的用法

2024-07-14 08:39:45
字體:
來源:轉載
供稿:網友

在上一章我們講解了ArrayBlockingQueue,用數組形式實現的阻塞隊列。

數組的長度在創建時就必須確定,如果數組長度小了,那么ArrayBlockingQueue隊列很容易就被阻塞,如果數組長度大了,就容易浪費內存。

而隊列這個數據結構天然適合用鏈表這個形式,而LinkedBlockingQueue就是使用鏈表方式實現的阻塞隊列。

一. 鏈表實現

1.1 Node內部類

  /**   * 鏈表的節點,同時也是通過它來實現一個單向鏈表   */  static class Node<E> {    E item;    // 指向鏈表的下一個節點    Node<E> next;    Node(E x) { item = x; }  }

有一個變量e儲存數據,有一個變量next指向下一個節點的引用。它可以實現最簡單地單向列表。

1.2 怎樣實現鏈表

   /**   * 它的next指向隊列頭,這個節點不存儲數據   */  transient Node<E> head;  /**   * 隊列尾節點   */  private transient Node<E> last;

要實現鏈表,必須有兩個變量,一個表示鏈表頭head,一個表示鏈表尾last。head和last都會在LinkedBlockingQueue對象創建的時候被初始化。

last = head = new Node<E>(null);

注意這里用了一個小技巧,鏈表頭head節點并沒有存放數據,它指向的下一個節點,才真正存儲了鏈表中第一個數據。而鏈表尾last的確儲存了鏈表最后一個數據。

1.3 插入和刪除節點

 /**   * 向隊列尾插入節點   */  private void enqueue(Node<E> node) {    // assert putLock.isHeldByCurrentThread(); // 當前線程肯定獲取了putLock鎖    // 將原隊列尾節點的next引用指向新節點node,然后再將node節點設置成隊列尾節點last    // 這樣就實現了向隊列尾插入節點    last = last.next = node;  }

在鏈表尾插入節點很簡單,將原隊列尾last的下一個節點next指向新節點node,再將新節點node賦值給隊列尾last節點。這樣就實現了插入一個新節點。

  // 移除隊列頭節點,并返回被刪除的節點數據  private E dequeue() {    // assert takeLock.isHeldByCurrentThread(); // 當前線程肯定獲取了takeLock鎖    // assert head.item == null;    Node<E> h = head;    // first節點中才存儲了隊列中第一個元素的數據    Node<E> first = h.next;    h.next = h; // help GC    // 設置新的head值,相當于刪除了first節點。因為head節點本身不儲存數據    head = first;    // 隊列頭的數據    E x = first.item;    // 移除原先的數據    first.item = null;    return x;  }

要注意head并不是鏈表頭,它的next才是指向鏈表頭,所以刪除鏈表頭也很簡單,就是將head.next賦值給head,然后返回原先head.next節點的數據。

刪除的時候,就要注意鏈表為空的情況。head.next的值使用enqueue方法添加的。當head.next==last的時候,表示已經刪除到最后一個元素了,當head.next==null的時候,就不能刪除了,因為鏈表已經為空了。這里沒有做判斷,是因為在調用dequeue方法的地方已經做過判斷了。

二. 同步鎖ReentrantLock和條件Condition

因為阻塞隊列在隊列為空和隊列已滿的情況下,都必須阻塞等待,那么就天然需要兩個條件。而為了保證多線程并發安全,又需要一個同步鎖。這個在ArrayBlockingQueue中已經說過了。

這里我們來說說LinkedBlockingQueue不一樣的地方。

  /** 獨占鎖,用于處理插入隊列操作的并發問題,即put與offer操作 */  private final ReentrantLock putLock = new ReentrantLock();  /** 隊列不滿的條件Condition,它是由putLock鎖生成的 */  private final Condition notFull = putLock.newCondition();  /** 獨占鎖,用于處理刪除隊列頭操作的并發問題,即take與poll操作 */  private final ReentrantLock takeLock = new ReentrantLock();  /** 隊列不為空的條件Condition, 它使用takeLock鎖生成的 */  private final Condition notEmpty = takeLock.newCondition();

2.1 putLock與takeLock

我們發現使用了兩把鎖:

  1. putLock 同步所有插入元素的操作,即put與offer系列方法的操作。
  2. takeLock 同步刪除和獲取元素的操作,即take與poll系列方法操作。

按照上面的說法,可能會出現同時插入元素和刪除元素的操作,那么就不會出現問題么?

我們來具體分析一個下,對于隊列來說操作分為三種:

  1. 在隊列尾插入元素。即put與offer系列方法,它們會涉及兩個變量,隊列中元素個數count,和隊列尾節點last。(不會涉及head節點)
  2. 移除隊列頭元素。即take與poll系列方法,它們會涉及兩個變量,隊列中元素個數count,和head節點。(不會涉及隊列尾節點last)
  3. 查看隊列頭元素。即 element()與peek()方法,它們會涉及兩個變量,隊列中元素個數count,和head節點。(不會涉及隊列尾節點last)

因此使用putLock鎖來保持last變量的安全,使用takeLock鎖來保持head變量的安全。

對于都涉及了隊列中元素個數count變量,所以使用AtomicInteger來保證并發安全問題。

  /** 隊列中元素的個數,這里使用AtomicInteger變量,保證并發安全問題 */  private final AtomicInteger count = new AtomicInteger();

2.2 notFull與notEmpty

  1. notFull 是由putLock鎖生成的,因為當插入元素時,我們要判斷隊列是不是已滿。
  2. notEmpty 是由takeLock鎖生成的,因為當刪除元素時,我們要判斷隊列是不是為空。

2.3 控制流程

當插入元素時:

  1. 先使用putLock.lockInterruptibly()保證只有一個線程進行插入操作.
  2. 然后利用count變量,判斷隊列是否已滿.
  3. 滿了就調用notFull.await()方法,讓當前線程等待。因為notFull是由putLock產生的,這里已經獲取到鎖了,所以可以調用await方法。
  4. 沒滿就調用 enqueue方法,向隊列尾插入新元素。
  5. 調用count.getAndIncrement()方法,將隊列中元素個數加一,并保證多線程并發安全。
  6. 調用signalNotEmpty方法,喚醒正在等待獲取元素的線程。

當刪除元素時:

  1. 先使用takeLock.lockInterruptibly()保證只有一個線程進行刪除操作.
  2. 然后利用count變量,判斷隊列是否為空.
  3. 隊列為空就調用notEmpty.await()方法,讓當前線程等待。因為notEmpty是由takeLock產生的,這里已經獲取到鎖了,所以可以調用await方法。
  4. 沒滿就調用 dequeue方法,刪除隊列頭元素。
  5. 調用count.getAndDecrement()方法,將隊列中元素個數減一,并保證多線程并發安全。
  6. 調用signalNotFull方法,喚醒正在等待插入元素的線程。

還要注意一下,Condition的signal和await方法必須在獲取鎖的情況下調用。因此就有了signalNotEmpty和signalNotFull方法:

  /**   * 喚醒在notEmpty條件下等待的線程,即移除隊列頭時,發現隊列為空而被迫等待的線程。   * 注意,因為要調用Condition的signal方法,必須獲取對應的鎖,所以這里調用了takeLock.lock()方法。   * 當隊列中插入元素(即put或offer操作),那么隊列肯定不為空,就會調用這個方法。   */  private void signalNotEmpty() {    final ReentrantLock takeLock = this.takeLock;    takeLock.lock();    try {      notEmpty.signal();    } finally {      takeLock.unlock();    }  }  /**   * 喚醒在notFull條件下等待的線程,即隊列尾添加元素時,發現隊列已滿而被迫等待的線程。   * 注意,因為要調用Condition的signal方法,必須獲取對應的鎖,所以這里調用了putLock.lock()方法   * 當隊列中刪除元素(即take或poll操作),那么隊列肯定不滿,就會調用這個方法。   */  private void signalNotFull() {    final ReentrantLock putLock = this.putLock;    putLock.lock();    try {      notFull.signal();    } finally {      putLock.unlock();    }  }

三. 插入元素方法

  public void put(E e) throws InterruptedException {    if (e == null) throw new NullPointerException();    // 記錄插入操作前元素的個數    int c = -1;    // 創建新節點node    Node<E> node = new Node<E>(e);    final ReentrantLock putLock = this.putLock;    final AtomicInteger count = this.count;    putLock.lockInterruptibly();    try {      //表示隊列已滿,那么就要調用notFull.await方法,讓當前線程等待      while (count.get() == capacity) {        notFull.await();      }      // 向隊列尾插入新元素      enqueue(node);      // 將當前隊列元素個數加1,并返回加1之前的元素個數。      c = count.getAndIncrement();      // c + 1 < capacity表示隊列未滿,就喚醒可能等待插入操作的線程      if (c + 1 < capacity)        notFull.signal();    } finally {      putLock.unlock();    }    // c == 0表示插入之前,隊列是空的。隊列從空到放入一個元素時,    // 才喚醒等待刪除的線程    // 防止頻繁獲取takeLock鎖,消耗性能    if (c == 0)      signalNotEmpty();  }

以put方法為例,大體流程與我們前面介紹一樣,這里有一個非常怪異的代碼,當插入完元素時,如果發現隊列未滿,那么調用notFull.signal()喚醒等待插入的線程。

大家就很疑惑了,一般來說,這個方法應該放在刪除元素(take系列的方法里),因為當我們刪除一個元素,那么隊列肯定是未滿的,那么調用notFull.signal()方法,喚醒等待插入的線程。

這里這么做主要是因為調用signal方法,必須先獲取對應的鎖,而在take系列的方法里使用的鎖是takeLock,那么想調用notFull.signal()方法,必須先獲取putLock鎖,這樣的話會性能就會下降,所以用了另一種方式。

  1. 首先我們應該知道signal方法,當有線程在這個條件下等待時,才會喚醒其中一個線程,當沒有線程等待時,這個方法相當于什么都沒做。所以這個方法的意義是可能會喚醒等待的一個線程。
  2. 當隊列未滿時,我們都調用notFull.signal()嘗試去喚醒一個等待插入線程。而且這里已經獲取putLock鎖了,所以不耗時。
  3. 但是有一個問題,當隊列已滿的時候,所有插入操作的線程,都會等待,就沒有機會調用notFull.signal()方法,那么喚醒這些等待線程呢?
  4. 喚醒這些線程的啟動條件,必須是由刪除元素操作觸發的,因為只有刪除隊列才會不滿。因為在take方法中 if (c == capacity) signalNotFull();

四. 刪除隊列頭元素

  public E take() throws InterruptedException {    E x;    int c = -1;    final AtomicInteger count = this.count;    final ReentrantLock takeLock = this.takeLock;    takeLock.lockInterruptibly();    try {      //表示隊列為空,那么就要調用notEmpty.await方法,讓當前線程等待      while (count.get() == 0) {        notEmpty.await();      }      // 刪除隊列頭元素,并返回它      x = dequeue();      // 返回當前隊列個數,然后將隊列個數減一      c = count.getAndDecrement();      // c > 1表示隊列不為空,就喚醒可能等待刪除操作的線程      if (c > 1)        notEmpty.signal();    } finally {      takeLock.unlock();    }    /**     * c == capacity表示刪除操作之前,隊列是滿的。只有從滿隊列中刪除一個元素時,     * 才喚醒等待插入的線程     * 防止頻繁獲取putLock鎖,消耗性能     */    if (c == capacity)      signalNotFull();    return x;  }

為什么調用notEmpty.signal()方法原因,對比一下我們在插入元素方法中的解釋。

五. 查看隊列頭元素

  // 查看隊列頭元素  public E peek() {    // 隊列為空,返回null    if (count.get() == 0)      return null;    final ReentrantLock takeLock = this.takeLock;    takeLock.lock();    try {      // 獲取隊列頭節點first      Node<E> first = head.next;      // first == null表示隊列為空,返回null      if (first == null)        return null;      else        // 返回隊列頭元素        return first.item;    } finally {      takeLock.unlock();    }  }

查看隊列頭元素,涉及到head節點,所以必須使用takeLock鎖。

六. 其他重要方法

6.1 remove(Object o)方法

  // 從隊列中刪除指定元素o  public boolean remove(Object o) {    if (o == null) return false;    // 因為不是刪除列表頭元素,所以就涉及到head和last兩個變量,    // putLock與takeLock都要加鎖    fullyLock();    try {      // 遍歷整個隊列,p表示當前節點,trail表示當前節點的前一個節點      // 因為是單向鏈表,所以需要記錄兩個節點      for (Node<E> trail = head, p = trail.next;         p != null;         trail = p, p = p.next) {        // 如果找到了指定元素,那么刪除節點p        if (o.equals(p.item)) {          unlink(p, trail);          return true;        }      }      return false;    } finally {      fullyUnlock();    }  }

從列表中刪除指定元素,因為刪除的元素不一定在列表頭,所以可能會head和last兩個變量,所以必須同時使用putLock與takeLock兩把鎖。因為是單向鏈表,需要一個輔助變量trail來記錄前一個節點,這樣才能刪除當前節點p。

6.2 unlink(Node<E> p, Node<E> trail) 方法

  // 刪除當前節點p,trail代表p的前一個節點  void unlink(Node<E> p, Node<E> trail) {    // 將當前節點的數據設置為null    p.item = null;    // 這樣就在鏈表中刪除了節點p    trail.next = p.next;    // 如果節點p是隊列尾last,而它被刪除了,那么就要將trail設置為last    if (last == p)      last = trail;    // 將元素個數減一,如果原隊列是滿的,那么就調用notFull.signal()方法    // 其實這個不用判斷直接調用的,因為這里肯定獲取了putLock鎖    if (count.getAndDecrement() == capacity)      notFull.signal();  }

要在鏈表中刪除一個節點p,只需要將p的前一個節點trail的next指向節點p的下一個節點next。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
色小说视频一区| 欧美成人午夜免费视在线看片| 欧美激情18p| 亚洲一区二区国产| 欧美在线视频免费播放| 欧美大成色www永久网站婷| 国产成人欧美在线观看| 亚洲天堂av网| 日韩av中文字幕在线| 日韩欧美第一页| 国产精品电影在线观看| 亚洲最新av在线| 国产小视频91| 国产精品 欧美在线| 久久香蕉精品香蕉| 中文字幕日韩精品有码视频| 久久久久99精品久久久久| 久久精品亚洲94久久精品| 欧美成人午夜激情视频| 97婷婷涩涩精品一区| 国产精品久久久久av| 久久精视频免费在线久久完整在线看| 久久国产精品影视| 亚洲最大的成人网| 精品偷拍一区二区三区在线看| 成人网在线免费观看| 黄网站色欧美视频| 欧美日韩国产麻豆| 欧美富婆性猛交| 日韩免费黄色av| 日韩电视剧在线观看免费网站| 成人乱人伦精品视频在线观看| 久久久久国产一区二区三区| 欧美视频在线免费看| 国产精品久久久久久久久久久新郎| 日韩精品免费在线观看| 欧美疯狂做受xxxx高潮| 久久精品2019中文字幕| 欧美做爰性生交视频| 97在线视频免费观看| 国产精品成人一区二区三区吃奶| 大桥未久av一区二区三区| 欧美激情视频一区| 在线观看不卡av| 久久免费精品视频| 欧美激情va永久在线播放| 免费91麻豆精品国产自产在线观看| 亚洲欧美日韩视频一区| 中文亚洲视频在线| 国产一区二区三区三区在线观看| 日韩成人中文电影| 91精品国产91久久久久久不卡| 亚洲社区在线观看| 亚洲人成网站在线播| 亚洲综合自拍一区| 51久久精品夜色国产麻豆| 亚洲欧美三级在线| 欧美一区二区三区免费视| 亚洲美女福利视频网站| 中文字幕久精品免费视频| 久久亚洲私人国产精品va| 国产成人欧美在线观看| 国产精品一区二区3区| 91av视频导航| 国产成人激情小视频| 最近2019中文免费高清视频观看www99| 91探花福利精品国产自产在线| 亚洲人成电影网站色www| 亚洲www永久成人夜色| 亚洲天堂日韩电影| 在线精品国产成人综合| 在线精品视频视频中文字幕| 91探花福利精品国产自产在线| 人妖精品videosex性欧美| 色偷偷9999www| 亚洲成人av在线播放| 日韩亚洲精品视频| 国产精品久久久久久五月尺| 久久福利视频网| 91av网站在线播放| 在线视频亚洲欧美| 欧美一区二区.| 久久久噜噜噜久噜久久| 久久99国产精品久久久久久久久| 国外日韩电影在线观看| 亚洲乱码国产乱码精品精| 日韩国产高清污视频在线观看| 亚洲激情 国产| 国产成人精品一区二区在线| 国产高清视频一区三区| 欧美黑人一级爽快片淫片高清| 欧美日韩视频免费播放| 91国在线精品国内播放| 亚洲精品一区二区网址| 国产精品视频一区二区高潮| 亚洲日韩中文字幕| 国产精品丝袜久久久久久不卡| 亚洲乱码国产乱码精品精| 亚洲国产成人久久| 91tv亚洲精品香蕉国产一区7ujn| 欧美黑人一区二区三区| 成人免费观看49www在线观看| 久久视频这里只有精品| 欧美日韩国产激情| 91视频8mav| 日韩精品极品毛片系列视频| 日韩欧美成人精品| 久久精品国产一区二区三区| 欧美性生活大片免费观看网址| 国产精品日日摸夜夜添夜夜av| 国产精品私拍pans大尺度在线| 国产高清视频一区三区| 成人激情春色网| 久久久久中文字幕| 国产精品99导航| 高潮白浆女日韩av免费看| 欧美成人精品一区二区三区| 色无极影院亚洲| 黄网站色欧美视频| 久久精品国产亚洲| 深夜成人在线观看| 国产suv精品一区二区三区88区| 色悠久久久久综合先锋影音下载| 亚洲天堂第一页| 亚洲97在线观看| 91精品国产91久久久久久吃药| 久久天天躁狠狠躁夜夜爽蜜月| 精品久久久av| 成人国产精品一区二区| 欧美美最猛性xxxxxx| 国产精品吴梦梦| 欧美日韩在线视频首页| 欧美疯狂性受xxxxx另类| 97视频在线观看播放| 精品久久久久久久中文字幕| 97精品久久久中文字幕免费| 色悠久久久久综合先锋影音下载| 国产香蕉97碰碰久久人人| 精品国产网站地址| 国产成+人+综合+亚洲欧美丁香花| 久久影视三级福利片| 国产精品成熟老女人| 亚洲第一精品久久忘忧草社区| 国产精品盗摄久久久| 国产欧美在线视频| 91久久久久久久久久| 日韩在线www| 中文字幕视频在线免费欧美日韩综合在线看| 成人午夜小视频| 中文日韩在线观看| 亚洲曰本av电影| 日韩毛片中文字幕| 日韩精品免费在线播放| 91中文字幕在线观看| 色偷偷亚洲男人天堂| 国产视频欧美视频| 久久九九全国免费精品观看| 51午夜精品视频| 国产日韩精品入口| www.99久久热国产日韩欧美.com| 亚洲精品美女久久久| 欧美老女人xx| 日韩成人在线视频| 日韩激情在线视频|