JUC---併發隊列源碼解析(JDK13)

java.util.concurrent包系列文章
JUC—ThreadLocal源碼解析(JDK13)
JUC—ThreadPoolExecutor線程池源碼解析(JDK13)
JUC—各種鎖(JDK13)
JUC—原子類Atomic*.java源碼解析(JDK13)
JUC—CAS源碼解析(JDK13)
JUC—ConcurrentHashMap源碼解析(JDK13)
JUC—CopyOnWriteArrayList源碼解析(JDK13)
JUC—併發隊列源碼解析(JDK13)
JUC—多線程下控制併發流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)


一、併發隊列

先看全家福

在這裏插入圖片描述
併發隊列又分爲阻塞隊列與非阻塞隊列

  • 實現了BlockingQueue的就是阻塞隊列,最下層左邊5個。隊列滿的時候放不進去,隊列空的時候null都取不出來,會阻塞。
  • 最右邊2個就是非阻塞隊列。

以* Deque結尾的是雙端隊列,頭和尾都能添加和刪除。雙進雙出。一般使用*Queue結尾的。Queue只能一段進一端出。


二、阻塞併發隊列

通常,應用於生產者消費者模型。阻塞隊列的一段給生產者用,一段給消費者用。阻塞隊列是線程安全的,所以生產者消費者都可以是多線程的。

在這裏插入圖片描述

方法

  • take():獲取並移除隊列頭結點,如果隊列沒有數據,則阻塞,直到隊列裏有數據
    在這裏插入圖片描述
  • put():插入數據,隊列滿了的話,則阻塞,直到隊列有空閒空間
    在這裏插入圖片描述
  • add():插入數據,隊列滿了的話,會拋出異常
  • remove():刪除數據,隊列爲空的話,會拋出異常
  • element():返回隊列頭元素,隊列爲空的話,會拋出異常
  • offer():添加一個元素,隊列滿了的話,會返回false
  • poll():取一個元素,隊列爲空的話,會返回null。取出的同時會刪除
  • peak():同poll一樣,不過取出時不會刪除
1、ArrayBlockingQueue
  • 有界的
  • 初始化需要指定容量
  • 公平:指定公平的話,等待最長時間的線程會優先處理

代碼實例在我的倉庫:https://github.com/MistraR/springboot-advance 包:com.advance.mistra.test.juc.queue

put方法

public void put(E e) throws InterruptedException {
	// 判空
    Objects.requireNonNull(e);
    final ReentrantLock lock = this.lock;
    // 加鎖,等待過程中可以被中斷
    lock.lockInterruptibly();
    try {
        while (count == items.length)
        	// 如果隊列滿了,則阻塞等待。這個nofFull是在初始化時生成的Condition對象
            notFull.await();
        // 隊列沒滿,則入隊
        enqueue(e);
    } finally {
    	// 解鎖
        lock.unlock();
    }
}
// ArrayBlockingQueue的部分屬性
final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}
2、LinkedBlockingQueue
  • 無界的,最大爲Integer.MAX_VALUE
  • 結構:Node包裝元素,有兩把鎖,takeLock和putLock

LinkedBlockingQueue

// 部分初始化參數
// 最大容量
private final int capacity;
// 原子類存儲當前隊列大小
private final AtomicInteger count = new AtomicInteger();

// 有2把鎖,put和take互不干擾
private final ReentrantLock takeLock = new ReentrantLock();
private final Condition notEmpty = takeLock.newCondition();
private final ReentrantLock putLock = new ReentrantLock();
private final Condition notFull = putLock.newCondition();

// 內部類Node
static class Node<E> {
  E item;
  
  Node<E> next;

  Node(E x) { item = x; }
}

put方法

 public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
     final int c;
     final Node<E> node = new Node<E>(e);
     final ReentrantLock putLock = this.putLock;
     final AtomicInteger count = this.count;
     // 使用的put鎖
     putLock.lockInterruptibly();
     try {
         /*
          * Note that count is used in wait guard even though it is
          * not protected by lock. This works because count can
          * only decrease at this point (all other puts are shut
          * out by lock), and we (or some other waiting put) are
          * signalled if it ever changes from capacity. Similarly
          * for all other uses of count in other wait guards.
          */
         while (count.get() == capacity) {
         	 // 如果隊列滿了,則阻塞
             notFull.await();
         }
         // 隊列沒滿,則入隊
         enqueue(node);
         // count+1
         c = count.getAndIncrement();
         if (c + 1 < capacity)
         	 // 當前容量還沒有滿,則喚醒一個在等待的put線程
             notFull.signal();
     } finally {
     	 // 釋放put鎖
         putLock.unlock();
     }
     if (c == 0)
         signalNotEmpty();
 }
3、PriorityBlockingQueue
  • 無界的
  • 支持優先級,不是先進先出
  • 自然排序的,插入的元素必須是可比較的
4、SynchronousQueue
  • 容量爲0,不存儲數據,只做直接交換
  • 是Executors.newCachedThreadPool()使用的阻塞隊列

在這裏插入圖片描述

5、DelayQueue
  • 無界的
  • 根據延遲時間排序
  • 元素必須實現Delayed接口,規定排序規則

三、非阻塞併發隊列

1、ConcurrentLinkedQueue

使用鏈表的結構,使用CAS非阻塞算法來保證線程安全。跟阻塞隊列用ReentrantLock保證併發安全不同。

offer方法

public boolean offer(E e) {
final Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
	// for死循環
    for (Node<E> t = tail, p = t;;) {
        Node<E> q = p.next;
        if (q == null) {
            // p is last node
            // p是尾節點,newNode就是Node保證之後的元素。
            // 直接CAS設置尾節點爲newNode,設置失敗則循環,直到CAS成功
            if (NEXT.compareAndSet(p, null, newNode)) {
                // Successful CAS is the linearization point
                // for e to become an element of this queue,
                // and for newNode to become "live".
                if (p != t) // hop two nodes at a time; failure is OK
                    TAIL.weakCompareAndSet(this, t, newNode);
                return true;
            }
            // Lost CAS race to another thread; re-read next
        }
        else if (p == q)
            // We have fallen off list.  If tail is unchanged, it
            // will also be off-list, in which case we need to
            // jump to head, from which all live nodes are always
            // reachable.  Else the new tail is a better bet.
            p = (t != (t = tail)) ? t : head;
        else
            // Check for tail updates after two hops.
            p = (p != t && t != (t = tail)) ? t : q;
    }
}

  • 我的公衆號:Coding摳腚
  • 偶爾發發自己最近學到的乾貨。學習路線,經驗,技術分享。技術問題交流探討。
    Coding摳腚
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章