Java多線程--阻塞隊列

如果隊列滿了,添加元素的線程將會陷入等待狀態,而隊列爲空,獲取元素的線程將會陷入等待。有了BlockingQueue,我們不需要關心什麼時候需要阻塞線程,什麼時候需要喚醒線程。這一切都交給了BlockingQueue。

  • ArrayBlockingQueue
    數組結構組成的遊街阻塞隊列
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();
    }

初始化需要傳遞容器大小,即數組大小,還有獲取元素的線程是否公平,即ReentrantLock構造方法中的參數。
添加元素

    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        checkNotNull(e);
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //等待時間
            while (count == items.length) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            //將元素添加到數組中,並且喚醒取線程
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }

\color{#FF0000}{主線邏輯:獲取鎖-》等待時間處理-》將元素添加到數組中-》喚醒取線程-》釋放鎖}

獲取元素方法

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //等待
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            //從數組中獲取元素,喚醒添加線程。
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

\color{#FF0000}{主線邏輯:獲取鎖-》等待時間處理-》獲取元素-》喚醒添加線程-》釋放鎖}

ArrayBlockingQueue與LinkedBlockingQueue
1ReentrantLock\color{#FF0000}{1 兩者都通過ReentrantLock保證隊列中元素線程安全性。}

  • LinkedBlockingQueue
    鏈表組成的有界隊列
    添加元素
    public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        if (e == null) throw new NullPointerException();
        long nanos = unit.toNanos(timeout);
        int c = -1;
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {
            	//當前鏈表容量等於最大容量,進入循環
                if (nanos <= 0)
                    return false;
                //陷入等待
                nanos = notFull.awaitNanos(nanos);
            }
            //可以添加元素
            enqueue(new Node<E>(e));
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                //添加元素後沒有達到最大的容量閾值
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            //c爲添加之前的鏈表元素個數。c=0說明添加元素之前,執行拿走線程陷入等待狀態,
            //需要喚醒他們去拿走添加的元素
            //可能之前元素充足,各個取線程沒有陷入等待,如果沒有c==0判斷,signalNotEmpty執行沒有意義,因爲壓根就沒有線程陷入等待
            // 參考https://blog.csdn.net/qq_26898645/article/details/95446943
            signalNotEmpty();
        return true;
    }

\color{#FF0000}{主線邏輯:獲取鎖-》添加元素-》喚醒添加線程-》釋放鎖-》獲取鎖-》喚醒取線程-》釋放鎖}

拉取元素方法

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        E x = null;
        int c = -1;
        long nanos = unit.toNanos(timeout);
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                if (nanos <= 0)
                    return null;
                // 鏈表元素個數爲空,陷入等待
                nanos = notEmpty.awaitNanos(nanos);
            }
            //出隊列
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                //喚醒拿線程
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            //喚醒添加線程
            signalNotFull();
        return x;
    }

\color{#FF0000}{主線邏輯:獲取鎖-》出隊-》喚醒取線程-》釋放鎖-》獲取鎖-》喚醒添加線程-》釋放鎖}

  • DelayQueue
    延遲無界阻塞隊列,類似於RabbitMq的延遲隊列,使用案例如下
		BlockingQueue<DelayedItem> blockingQueue=new DelayQueue<>();
		//設置延遲5秒鐘
		blockingQueue.add(new DelayedItem(5*1000L));
		long beginTime = System.currentTimeMillis();
		System.out.println(beginTime);
		DelayedItem poll = blockingQueue.poll(60,TimeUnit.SECONDS);
		System.out.println("延遲時間:"+(System.currentTimeMillis()-beginTime));
		System.out.println("獲取到的任務,延遲時間:"+poll.getDelayTime());
	public static class DelayedItem implements Delayed{
	
		/**
		 * 延遲時間
		 */
		private Long delayTime;

		/**
		 * 到期時間
		 */
		private Long expireTime;
		
		public DelayedItem(Long delayTime) {
			this.delayTime = delayTime;
			this.expireTime=System.currentTimeMillis()+delayTime;
		}

		public Long getDelayTime() {
			return delayTime;
		}

		@Override
		public int compareTo(Delayed o) {
			return (int) (this.getDelay(TimeUnit.MILLISECONDS)-o.getDelay(TimeUnit.MILLISECONDS));
		}

		@Override
		public long getDelay(TimeUnit unit) {
			return expireTime-System.currentTimeMillis();
		}
	}

DelayQueue源碼解析
添加任務

public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //將e添加到合適的位置,如果構造DelayQueue定義了比較器,會用到Comparator,沒有定義比較器,那就用到實現的compareTo方法
            //決定那個任務放到隊列中的先後順序。
            q.offer(e);
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

ReentrantLockcompareTo\color{#FF0000}{通過ReentrantLock保證線程安全,將任務添加到隊列中,涉及到排序的位置,用到了實現的compareTo方法}
獲取數據

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            for (;;) {
                E first = q.peek();
                if (first == null) {
                    if (nanos <= 0)
                        return null;
                    else
                        //沒有任務,陷入等待,
                        nanos = available.awaitNanos(nanos);
                } else {
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        //獲取到的任務延遲時間到了,拉取出來。
                        return q.poll();
                    if (nanos <= 0)
                        //等待的時間到了,直接返回。
                        return null;
                    first = null; // don't retain ref while waiting
                    if (nanos < delay || leader != null)
                        //任務的延遲時間比等待的數據長,再等待一會。
                        nanos = available.awaitNanos(nanos);
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            //任務的延遲時間比等待的數據短,等待任務的延遲數據,
                            long timeLeft = available.awaitNanos(delay);
                            nanos -= delay - timeLeft;
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();
        }
    }

\color{#FF0000}{}

參考:
https://my.oschina.net/u/3081871/blog/1790780
http://www.pianshen.com/article/4481304862/

SynchronousQueue
不存儲元素的阻塞隊列

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