Davids原理探究:Java併發包中併發隊列(ConcurrentLinkedQueue、LinkedBlockingQueue、DelayQueue...)

原理探究:Java併發包中併發隊列

關注可以查看更多粉絲專享blog~

ConcurrentLinkedQueue

線程安全的無界非阻塞隊列,由單向鏈表實現,入隊出隊使用CAS來保證線程安全。
ConcurrentLinkedQueue

  1. 無界隊列
  2. 無鎖算法,入隊和出隊使用CAS算法進行設置隊首和隊尾元素
  3. 由於是無鎖算法,所以在獲取size的時候是進行遍歷操作的,在遍歷過程中,已經遍歷過的節點可能有增刪,所以size在高併發場景下存在一定誤差,而且size性能較差,所以如果只是判斷隊列是否有元素建議使用isEmpty(),該方法只會獲取first節點是否有元素。
  4. 初始化隊列時頭尾節點均指向哨兵節點head = tail = new Node(null);
public int size() {
  int count = 0;
  	// 循環遍歷,效率低,由於是無鎖算法,所以size在高併發情況下不準確
    for (Node<E> p = first(); p != null; p = succ(p))
        if (p.item != null)
            // Collection.size() spec says to max out
            if (++count == Integer.MAX_VALUE)
                break;
    return count;
}

(題外話:在判斷字符串是否爲空的時候慣用寫法是(null == str || “”.equals(str))其實String的equals方法也是遍歷字符串,建議使用(cs == null || cs.length() == 0),還有習慣用StringUtils的,org.apache.commons.lang3.StringUtils和org.springframework.util.StringUtils是不一樣的,spring使用的是equals,lang3使用的是length,建議自己封裝StringUtils去繼承lang3,後期可以根據自己需要去修改繼承或者覆蓋方法)

LinkedBlockingQueue

有界阻塞隊列,由單向鏈表和獨佔鎖實現。
LinkedBlockingQueue

  1. 生產消費模型
  2. 有界隊列Integer.MAX_VALUE,也可以在new的時候自行制定capacity
  3. 加鎖,take鎖和put鎖,notFull Condition條件隊列控制生產者,notEmpty Condition條件隊列控制消費者
    1. put鎖:在每次執行offer(無阻塞尾部入隊enqueue(node),已滿則丟棄)、put(while阻塞入隊,若隊列滿則等待,可被中斷,中斷後拋出InterruptedException)後若隊列沒有滿則調用notFull.signal()喚醒生產者條件隊列進行first節點進行生產,unlock之後判斷操作前如果隊列中沒有元素(count.getAndIncrement() == 0)則會調用signalNotEmpty喚醒消費者條件隊列的first節點進行消費;
    2. take鎖:在每次執行poll(無阻塞頭部出隊dequeue(node))、take(while阻塞頭部出隊,若隊列空則等待,可被中斷,中斷後拋出InterruptedException)後若隊列還有數據則調用notEmpty.signal()喚醒消費者條件隊列的first節點進行消費,unlock之後判斷操作前如果隊列是否滿(count.getAndDecrement() == capacity)則會調用signalNotFull喚醒生產者條件隊列的first節點進行消費;
    3. take鎖:peek操作與poll操作相似,但只是窺視,不出隊。
    4. 雙重加鎖(put、take鎖):remove遍歷查找,找到則調用unlink刪除,並鏈接前置節點和後置節點,操作完之後如果隊列是否滿(count.getAndDecrement() == capacity)則會調用signalNotFull喚醒生產者條件隊列的first節點進行消費;
    5. size使用AtomicInteger存儲,結果準確,獲取size複雜度O(1)。
public boolean offer(E e) {
	// 爲空直接拋出空指針
   if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;
    // 如果隊列已滿直接返回false
    if (count.get() == capacity)
        return false;
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    // 獲取put鎖
    putLock.lock();
    try {
    	// 如果隊列未滿直接調用enqueue入隊,count+1
        if (count.get() < capacity) {
        	// put(E e)與offer區別在於put在獲取可中斷鎖(putLock.lockInterruptibly();)
        	// count.get() == capacity時while阻塞,offer無阻塞直接返回
        	// enqueue方法: last = last.next = node;
            enqueue(node);
            c = count.getAndIncrement();
            // 入隊後如果隊列未滿則喚醒生產者
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
    	// 釋放鎖
        putLock.unlock();
    }
    if (c == 0)
    	// 如果添加前隊列爲空則喚醒消費者消費當前入隊元素
        signalNotEmpty();
    return c >= 0;
}

ArrayBlockingQueue

有界阻塞隊列,由數組和獨佔鎖實現。
ArrayBlockingQueue

  1. 由數組實現的有界隊列。
  2. 獨佔鎖ReentrantLock,同時只能有一個線程進行入隊/出隊操作,由入隊指針(putIndex)和出隊指針(takeIndex)記錄入隊/出隊元素位置。
  3. offer(無阻塞尾部入隊enqueue(node),已滿則丟棄)、put(while阻塞入隊,若隊列滿則調用notFull.await()放入notFull條件隊列等待,可被中斷,中斷後拋出InterruptedException)入隊成功之後計算隊尾指針++putIndex,count++,並調用notEmpty.signal()喚醒消費者。
  4. poll(無阻塞頭部出隊dequeue(node)),take(while阻塞頭部出隊,若隊列空則等待,可被中斷,中斷後拋出InterruptedException)出隊成功之後計算隊尾指針++takeIndex,count–,並調用notFull.signal()喚醒生產者。
  5. peek(無阻塞窺視,獲取頭部元素不移除)。
  6. size(加鎖獲取count值,count沒有被volatile修飾,因爲操作count的時候都是在獲取鎖之後,所以沒有加volatile,獲取size的時候同理,使用鎖來保證內存可見性)。

PriorityBlockingQueue

帶優先級的無界阻塞隊列,內部使用平衡二叉堆實現,默認使用對象的compareTo方法提供比較規則,可以自定義comparators。
PriorityBlockingQueue

  1. 由數組實現的無界優先級隊列,因爲無界所以只有notEmpty條件隊列控制消費者。
  2. allocationSpinLock是個自旋鎖,使用CAS操作保證同時只有一個線程進行擴容,狀態0/1,0表示當前沒有擴容,1表示當前正在擴容。擴容前會unlock,其實也可以不unlock,因爲擴容需要時間,爲了得到更好的性能,在擴容完成後,再獲取鎖,將當前queue裏面的元素複製到新數組。
  3. siftUpComparable、siftDownComparable,二叉樹堆維護優先級。
private static <T> void siftUpComparable(int k, T x, Object[] array) {
    Comparable<? super T> key = (Comparable<? super T>) x;
    while (k > 0) {
        int parent = (k - 1) >>> 1;
        Object e = array[parent];
        if (key.compareTo((T) e) >= 0)
            break;
        array[k] = e;
        k = parent;
    }
    array[k] = key;
}

private static <T> void siftDownComparable(int k, T x, Object[] array, int n) {
  if (n > 0) {
        Comparable<? super T> key = (Comparable<? super T>)x;
        int half = n >>> 1;           // loop while a non-leaf
        while (k < half) {
            int child = (k << 1) + 1; // assume left child is least
            Object c = array[child];
            int right = child + 1;
            if (right < n &&
                ((Comparable<? super T>) c).compareTo((T) array[right]) > 0)
                c = array[child = right];
            if (key.compareTo((T) c) <= 0)
                break;
            array[k] = c;
            k = child;
        }
        array[k] = key;
    }
}
  1. put內部調用offer由於是無界隊列,所以不需要阻塞。
  2. poll:獲取對頭元素。
  3. take:阻塞出隊,直到有元素位置,可被中斷,中斷後拋出InterruptedException。
  4. peek:獲取鎖之後,return (size == 0) ? null : (E) queue[0];
  5. size:獲取所之後返回size,複雜度O(1)。
  6. 示例,優先級隊列創建類實現Comparable接口,並重寫compareTo,自定義元素比較規則,取出順序和先後順序無關,只與優先級有關。
/**
 * PriorityBlockingQueue 示例
 **/
public class PriorityBlockingQueueTest {

	/**
     * 實現Comparable接口,getDelay和compareTo方法
     **/
    @Data // lombok
    static class Task implements Comparable<Task> {

        private int priority = 0;
        private String taskName;

		/**
	     * 實現compareTo方法
	     **/
        @Override
        public int compareTo( Task o ) {
            return this.priority >= o.priority ? 1 : -1;
        }
    }

    public static void main( String[] args ) {
        PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            Task task = new Task();
            task.setPriority(random.nextInt(10));
            task.setTaskName("taskName" + i);
            tasks.offer(task);
        }

        while (!tasks.isEmpty()) {
            Task poll = tasks.poll();
            System.out.println(poll);
        }

    }

}

DelayQueue

無界阻塞延時隊列,使用獨佔鎖實現線程同步,隊列中元素需要實現Delayed接口。
DelayQueue

  1. 併發無界阻塞延時隊列,頭部爲最快要過期的元素。
  2. private final PriorityQueue q = new PriorityQueue();用於存放元素。leader變量基於Leader-Follower模式的變體,用於儘量減少不必要的線程等待。
  3. offer:獲取鎖然後q.offer入隊,插入元素要實現Delayed接口,入隊完成後peek獲取元素,若peek() == e,則將leader置空並喚醒Condition available = lock.newCondition()的一個follwer線程,告訴它隊列裏面有元素了。
  4. take:獲取並移除隊列裏面延遲時間過期的元素,如果隊列沒有過期元素則等待。leader不爲null這說明有其它線程在執行take。如果隊首元素未到過期時間,並且leader線程是當前線程,則會調用awaitNanos(delay),休眠delay時間(這期間會釋放鎖,所以其他線程可以執行offer操作,也可以take阻塞自己),剩餘時間過期後當前線程會重新競爭到鎖,然後重置leader線程爲null,重新進入for循環這時候會發現隊首元素已經過期了,則會直接返回隊首元素。
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    // 獲取可中斷鎖
    lock.lockInterruptibly();
    try {
    	// 循環操作
        for (;;) {
        	// 獲取隊首元素,不出隊
            E first = q.peek();
            if (first == null)
            	// 隊首元素爲空則等待,並釋放鎖
                available.await();
            else {
            	// 隊首元素不爲空,則獲取過期時間
                long delay = first.getDelay(NANOSECONDS);
                if (delay <= 0)
                	// 若已過期則直接取出並返回
                    return q.poll();
                // 若未到期,則釋放引用
                first = null; // don't retain ref while waiting
                if (leader != null)
                	// 如果leader不爲null,則說明有其他縣城在執行take操作,此時休眠自己
                    available.await();
                else {
                	// 如果未過期切leader爲null,則將leader設置爲當前線程
                    Thread thisThread = Thread.currentThread();
                    leader = thisThread;
                    try {
                    	// 休眠等待delay時間,這期間會釋放鎖,所以其他線程可以執行offer操作,也可以take阻塞自己,
                    	// 這期間會釋放鎖,所以其他線程可以執行offer操作,也可以take阻塞自己,剩餘時間過期後
                    	// 當前線程會重新競爭到鎖,然後重置leader線程爲null
                    	// 重新進入for循環這時候會發現隊首元素已經過期了,則會直接返回隊首元素
                        available.awaitNanos(delay);
                    } finally {
                        if (leader == thisThread)
                            leader = null;
                    }
                }
            }
        }
    } finally {
    	// 操作完成後將leader設置爲null,如果隊列還有元素,則喚醒條件等待隊列中的隊首線程進行操作
        if (leader == null && q.peek() != null)
            available.signal();
        // 釋放鎖
        lock.unlock();
    }
}
  1. poll操作比較簡單,獲取非中斷鎖後,無阻塞peek隊首元素,若爲null或者沒有過期直接返回。
public E poll() {
final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E first = q.peek();
        if (first == null || first.getDelay(NANOSECONDS) > 0)
            return null;
        else
            return q.poll();
    } finally {
        lock.unlock();
    }
}
  1. size操作,獲取隊列元素個數,包含過期和未過期的,複雜度O(1)。

/**
 * DelayQueueTest 示例
 **/
public class DelayQueueTest {

	/**
     * 實現Delayed接口,getDelay和compareTo方法
     **/
    @Data // lombok
    static class DelayQueueEle implements Delayed {
		// 延時時間
        private final int delayTime;
        // 到期時間
        private final long expire;
        // 任務名稱
        private String taskName;

        public DelayQueueEle( int delayTime, String taskName ) {
            this.delayTime = delayTime;
            this.taskName = taskName;
            this.expire = System.currentTimeMillis() + delayTime;
        }

		/**
         * 實現getDelay方法
         **/
        @Override
        public long getDelay( TimeUnit unit ) {
            return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

		/**
         * 實現compareTo方法
         **/
        @Override
        public int compareTo( Delayed o ) {
            return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
        }
    }

    public static void main( String[] args ) {
    	// 創建delay隊列
        DelayQueue<DelayQueueEle> delayeds = new DelayQueue<>();
        // 創建延時任務
        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            delayeds.offer(new DelayQueueEle(random.nextInt(500), "taskName:" + i));
        }
		// 依次取出並打印
        DelayQueueEle ele;
        // 循環,防止虛假喚醒,則不能打印全部元素
        try {
            for (; ; ) {
                while ((ele = delayeds.take()) != null) {
                    System.out.println(ele);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        或者
//        for (; ; ) {
//            while ((ele = delayeds.poll()) != null) {
//                System.out.println(ele);
//            }
//        }
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章