一.BlockingQueue:
BlockingQueue是java.util.concurrent包下的一個隊列類,從1.5開始,用於解決高併發環境中的隊列應用,主要是生產者消費者問題
支持兩個附加操作的Queue,這兩個操作是獲取元素時等待隊列變爲非空,以及存儲元素時等待空間變得可用。
也就是:
-
阻塞添加:當隊列滿了之後又有元素想要添加進來,添加元素的線程會被阻塞,直到隊列非滿之後,喚醒添加線程,元素被添加進來
-
阻塞刪除:當隊列空了之後又有線程想刪除元素,該線程會被阻塞,直到隊列非空之後,喚醒刪除元素的線程,元素被刪除。
來看看BlockingQueue接口裏的方法:
boolean add(E e);
/*將指定元素插入此隊列中(如果立即可行且不會違反容量限制),成功時返回 true,如果當前沒有可用
的空間,則拋出 IllegalStateException。*/
boolean offer(E e);
/**
將指定元素插入此隊列中(如果立即可行且不會違反容量限制),成功時返回 true,如果當前沒有可用的
空間,則返回 false。
**/
boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException;
/** 將指定元素插入此隊列中,在到達指定的等待時間前等待可用的空間(如果有必要)。
* Retrieves and removes the head of this queue, waiting if necessary
* until an element becomes available.
*
* @return the head of this queue
* @throws InterruptedException if interrupted while waiting
*/
E take() throws InterruptedException;
/**獲取並移除此隊列的頭部,在元素變得可用之前一直等待(如果有必要)。
*/
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
/**獲取並移除此隊列的頭部,在指定的等待時間前等待可用的元素(如果有必要)。
*/
int remainingCapacity();
/**
返回在無阻塞的理想情況下(不存在內存或資源約束)此隊列能接受的附加元素數量;如果沒有內部
限制,則返回 Integer.MAX_VALUE。
**/
boolean remove(Object o);
/**
從此隊列中移除指定元素的單個實例(如果存在)。
**/
public boolean contains(Object o);
/**
如果此隊列包含指定元素,則返回 true。
*/
int drainTo(Collection<? super E> c);
/**
移除此隊列中所有可用的元素,並將它們添加到給定 collection 中。
*/
int drainTo(Collection<? super E> c, int maxElements);
/**
最多從此隊列中移除給定數量的可用元素,並將這些元素添加到給定 collection 中。
**/
這裏我們把上述操作進行分類
插入方法:
add(E e) : 添加成功返回true,失敗拋IllegalStateException異常
offer(E e) : 成功返回 true,如果此隊列已滿,則返回 false。
put(E e) :將元素插入此隊列的尾部,如果該隊列已滿,則一直阻塞
刪除方法:
remove(Object o) :移除指定元素,成功返回true,失敗返回false
poll() : 獲取並移除此隊列的頭元素,若隊列爲空,則返回 null
take():獲取並移除此隊列頭元素,若沒有元素則一直阻塞。
檢查方法
element() :獲取但不移除此隊列的頭元素,沒有元素則拋異常
peek() :獲取但不移除此隊列的頭;若隊列爲空,則返回 null。
BlockingQueue 的實現類:
我們先用ArrayBlockingQueue實現一個生產者消費者的模型:
package com;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class BlockQueue {
public static void main(String[] args) {
ArrayBlockingQueue<Colo> queue = new ArrayBlockingQueue<Colo>(5);
for (int i = 0; i < 5; i++) {
new Thread(new Producer(queue)).start();
}
for (int i = 0; i < 5; i++) {
new Thread(new Consumer(queue)).start();
}
}
}
class Colo {
}
class Consumer implements Runnable {
private ArrayBlockingQueue<Colo> queue;
public Consumer(ArrayBlockingQueue<Colo> queue) {
this.queue = queue;
}
public void consume() {
try {
System.out.println(Thread.currentThread().getName() + "消費了" + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
// TimeUnit.SECONDS.sleep(1000);
while (true) {
Thread.sleep(1000);
consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Producer implements Runnable {
private final ArrayBlockingQueue<Colo> queue;
Producer(ArrayBlockingQueue<Colo> queue) {
this.queue = queue;
}
public void consume() {
Colo c = new Colo();
try {
queue.put(c);
System.out.println(Thread.currentThread().getName() + "生產了" + c);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(1000);
;
consume();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
Thread-2生產了com.Colo@b88b7fb
Thread-4生產了com.Colo@5585ed8f
Thread-7消費了com.Colo@5585ed8f
Thread-6消費了com.Colo@b88b7fb
Thread-9消費了com.Colo@6ea3659
Thread-5消費了com.Colo@5da9d59
Thread-3生產了com.Colo@6ea3659
Thread-0生產了com.Colo@531e98a5
Thread-8消費了com.Colo@531e98a5
Thread-1生產了com.Colo@5da9d59
Thread-5消費了com.Colo@b1d7392
Thread-2生產了com.Colo@54f58fa
Thread-9消費了com.Colo@5721161e
Thread-8消費了com.Colo@5b42194f
**/
接下來探究ArrayBlockingQueue的實現原理:
- 一個有限的blocking queue由數組支持。 這個隊列排列元素FIFO(先進先出)。 隊列的頭部是隊列中最長的元素。 隊列的尾部是隊列中最短時間的元素。 新元素插入隊列的尾部,隊列檢索操作獲取隊列頭部的元素。
這是一個經典的“有界緩衝區”,其中固定大小的數組保存由生產者插入的元素並由消費者提取。 創建後,容量無法更改。 嘗試
put
成滿的隊列的元件將導致在操作阻擋; 嘗試take
從空隊列的元件將類似地阻塞。此類支持可選的公平策略,用於訂購等待的生產者和消費者線程。 默認情況下,此訂單不能保證。 然而,以公平設置爲
true
的隊列以FIFO順序授予線程訪問權限。 公平性通常會降低吞吐量,但會降低變異性並避免飢餓。該類及其迭代器實現了
Collection
和Iterator
接口的所有可選方法。這個類是Java Collections Framework的成員 --------------------------------From Jdk 1.8 api
他的屬性有:
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
implements BlockingQueue<E>, java.io.Serializable {
/** 存儲數據的數組 */
final Object[] items;
/**獲取數據的索引,主要用於take,poll,peek,remove方法 */
int takeIndex;
/**添加數據的索引,主要用於 put, offer, or add 方法*/
int putIndex;
/** 隊列元素的個數 */
int count;
/** 控制並非訪問的鎖 */
final ReentrantLock lock;
/**notEmpty條件對象,用於通知take方法隊列已有元素,可執行獲取操作 */
private final Condition notEmpty;
/**notFull條件對象,用於通知put方法隊列未滿,可執行添加操作 */
private final Condition notFull;
/**
迭代器
*/
transient Itrs itrs = null;
}
ArrayBlockingQueue的內部是通過一個可重入鎖ReentrantLock和兩個Condition條件對象來實現阻塞,這裏先看看其內部成員變量
他的主要方法:
boolean add(E e)
在插入此隊列的尾部,如果有可能立即這樣做不超過該隊列的容量,返回指定的元素 true成功時與拋出 IllegalStateException如果此隊列已滿。
boolean offer(E e)
如果可以在不超過隊列容量的情況下立即將其指定的元素插入該隊列的尾部,則在成功時 false如果該隊列已滿,則返回 true 。
boolean offer(E e, long timeout, TimeUnit unit)
在該隊列的尾部插入指定的元素,等待指定的等待時間,以使空間在隊列已滿時變爲可用。
E peek()
檢索但不刪除此隊列的頭,如果此隊列爲空,則返回 null 。
E poll()
檢索並刪除此隊列的頭,如果此隊列爲空,則返回 null 。
E poll(long timeout, TimeUnit unit)
檢索並刪除此隊列的頭,等待指定的等待時間(如有必要)使元素變爲可用。
void put(E e)
在該隊列的尾部插入指定的元素,如果隊列已滿,則等待空間變爲可用。
E take()
檢索並刪除此隊列的頭,如有必要,等待元素可用。
E put(E e)
在該隊列的尾部插入指定的元素,如果隊列已滿,則等待空間變爲可用。
那麼他是如何通過ReentrantLock 和 兩個Condition來實現阻塞的呢
先自己用condition來寫一個生產者與消費者的模型:
package com.company.bank;
import java.util.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Condition2 {
private static final Lock lock = new ReentrantLock();
private static final Condition un_empty = lock.newCondition();
private static final Condition un_full = lock.newCondition();
static private Queue<Integer> queue = new LinkedList<Integer>();
public static void main(String[] args) throws InterruptedException {
Consumer consumer = new Consumer();
Produce produce = new Produce();
consumer.start();
produce.start();
Thread.sleep(30000);
consumer.interrupt();
produce.interrupt();
}
static class Consumer extends Thread {
@Override
public void run() {
super.run();
while(true){
consume();
}
}
private void consume() {
lock.lock();
try {
while (queue.size()==0) {
System.out.println("隊列空,阻塞消費線程");
un_empty.await();
}
queue.poll();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "取走一個元素" + "還剩" + queue.size());
System.out.println("喚醒生產線程");
un_full.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
static class Produce extends Thread {
@Override
public void run() {
super.run();
while(true) {
produce();
}
}
private void produce() {
lock.lock();
try {
while (queue.size() == 10) {
System.out.println("隊列已滿,生產者被阻塞,等待空間");
un_full.await();
}
queue.offer(1);
Thread.sleep(1000);
System.out.println("向隊列取中插入一個元素,隊列剩餘空間:" + (10 - queue.size()));
un_empty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
之前拿ArrayBlockingQueue api 實現的生產者消費者是利用了他的兩個阻塞方法 take 和 put 實現的,對比自己手寫的condition 實現的生產者消費者模型,其實他們底層是一模一樣的,知道怎麼寫基礎版的就能知道ArrayBlockingQueue底層原理
首先來看看put()的源碼:
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
這裏一旦隊列滿了就用notfull將線程掛起,下面出現了一個enqueue的私有方法:
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
注意這裏就出現了notEmpty.signal()這裏基本和手寫的實現邏輯一模一樣了。
private void produce() {
lock.lock();
try {
while (queue.size() == 10) {
System.out.println("隊列已滿,生產者被阻塞,等待空間");
un_full.await();
}
queue.offer(1);
Thread.sleep(1000);
System.out.println("向隊列取中插入一個元素,隊列剩餘空間:" + (10 - queue.size()));
un_empty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
那麼再看看take的實現源碼:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();//隊列一旦滿就需要不空,此時被掛起,需要消費者的消費然後用notempty.signal喚醒生產者線程
return dequeue();
} finally {
lock.unlock();
}
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}