在JDK1.5新加入了一個包concurrent,位於java.util.concurrent。在我們寫業務代碼的時候,可能最爲常見就是ConcurrentHashMap。當然今天我們的主角不是他,而是queue。在併發隊列上JDK提供了兩套實現。
阻塞隊列(IO):以BlockingQueue接口爲代表的阻塞隊列。
非阻塞(NIO)隊列:以ConcurrentLinkedQueue爲代表的高性能隊列。
1.阻塞隊列(IO)
1.1.什麼是阻塞隊列
1、當隊列是滿時,往隊列裏添加元素的操作會被阻塞;
2、或者當隊列是空的時,從隊列中獲取元素的操作將會被阻塞;
3、阻塞隊列是線程安全的。
1.2.阻塞隊列的應用場景
阻塞隊列常用於生產者和消費者的場景。
1、生產者是往隊列裏添加元素的線程;
2、消費者是從隊列裏拿元素的線程;
3、阻塞隊列就是生產者存放元素的容器。
1.3.BlockingQueue
1.3.1.ArrayBlockingQueue
1、是一個有邊界的阻塞隊列,它的內部實現是一個數組。有邊界的意思是它的容量是有限的,我們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變。
2、採用FIFO先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。
1.3.2.ArrayBlockingQueue
1、LinkedBlockingQueue阻塞隊列大小的配置是可選的,如果我們初始化時指定一個大小,它就是有邊界的,如果不指定,它就是無邊界的。說是無邊界,其實是採用了默認大小爲Integer.MAX_VALUE的容量 。它的內部實現是一個鏈表。
2、和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。
1.3.3.PriorityBlockingQueue
1、是一個沒有邊界的隊列,它的排序規則和 java.util.PriorityQueue一樣
2、需要注意,PriorityBlockingQueue中允許插入null對象。
3、所有插入PriorityBlockingQueue的對象必須實現 java.lang.Comparable接口,隊列優先級的排序規則就是按照我們對這個接口的實現來定義的。
4、我們可以從PriorityBlockingQueue獲得一個迭代器Iterator,但這個迭代器並不保證按照優先級順序進行迭代。
1.3.4.SynchronousQueue
僅允許容納一個元素。當一個線程插入一個元素後會被阻塞,除非這個元素被另一個線程消費。
2.非阻塞(NIO)隊列
和阻塞隊列相比,他不會被阻塞,並且效率比阻塞高。
2.1.ConcurrentLinkedQueue
ConcurrentLinkedQueue是一個適用於高併發場景下的隊列,通過無鎖的方式,實現了高併發狀態下的高性能。通常ConcurrentLinkedQueue性能好於BlockingQueue。它是一個基於鏈接節點的無界線程安全隊列。
該隊列的元素遵循先進先出的原則。頭是最先加入的,尾是最近加入的,該隊列不允許null元素。
2.2.1重要方法
add 和offer() 都是加入元素的方法(在ConcurrentLinkedQueue中這倆個方法沒有任何區別),在這兩個方法中,均調用offerLast(E e)方法。
將指定的元素插入此雙端隊列的末尾
/**
* Inserts the specified element at the tail of this deque.
* As the deque is unbounded, this method will never throw
* {@link IllegalStateException} or return {@code false}.
*
* @return {@code true} (as specified by {@link Collection#add})
* @throws NullPointerException if the specified element is null
*/
public boolean add(E e) {
return offerLast(e);
}
/**
* Inserts the specified element at the tail of this deque.
* As the deque is unbounded, this method will never return {@code false}.
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
return offerLast(e);
}
poll() 和peek() 都是取頭元素節點,區別在於前者會刪除元素,後者不會。
public E poll() { return pollFirst(); }
public E peek() { return peekFirst(); }
返回刪除此雙端隊列的第一個元素
/**
* Retrieves and removes the first element of this deque,
* or returns {@code null} if this deque is empty.
*
* @return the head of this deque, or {@code null} if this deque is empty
*/
public E pollFirst() {
restart: for (;;) {
for (Node<E> first = first(), p = first;;) {
final E item;
if ((item = p.item) != null) {
// recheck for linearizability
if (first.prev != null) continue restart;
if (ITEM.compareAndSet(p, item, null)) {
unlink(p);
return item;
}
}
if (p == (p = p.next)) continue restart;
if (p == null) {
if (first.prev != null) continue restart;
return null;
}
}
}
}
返回此雙端隊列的第一個元素
/**
* Retrieves, but does not remove, the first element of this deque,
* or returns {@code null} if this deque is empty.
*
* @return the head of this deque, or {@code null} if this deque is empty
*/
public E peekFirst() {
restart: for (;;) {
E item;
Node<E> first = first(), p = first;
while ((item = p.item) == null) {
if (p == (p = p.next)) continue restart;
if (p == null)
break;
}
// recheck for linearizability
if (first.prev != null) continue restart;
return item;
}
}
2.2.2代碼演示
@Test
public void test() throws Exception {
ConcurrentLinkedDeque<String> qStr = new ConcurrentLinkedDeque<>();
qStr.add("1胡濤");
qStr.add("2謝天");
qStr.add("3楊騰龍");
System.out.println("當前隊列:"+qStr.toString());
String poll2 = qStr.peek();
System.out.println("調用peek()方法獲取到"+poll2);
System.out.println("調用poll()方法後,隊列還有:"+qStr.toString());
String poll = qStr.poll();
System.out.println("調用poll()方法獲取到"+poll);
System.out.println("調用poll()方法後,隊列還有:"+qStr.toString());
}
3.阻塞隊列的生產消費者應用
基礎知識儲備:你需要多線程的知識點,這裏我就不囉嗦多線程的知識點了。
AtomicInteger:高併發的情況下,i++無法保證原子性,往往會出現問題,所以引入AtomicInteger類。
volatile:1、保證共享變量對所有的線程的可見;2、禁止CPU進行重排序
生產者
class ProducerThread implements Runnable {
private BlockingQueue<String> blockingQueue;
private AtomicInteger count = new AtomicInteger();
private volatile boolean FLAG = true;
public ProducerThread(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "生產者開始啓動....");
while (FLAG) {
String data = count.incrementAndGet() + "";
try {
boolean offer = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
if (offer) {
System.out.println(Thread.currentThread().getName() + ",生產隊列" + data + "成功..");
} else {
System.out.println(Thread.currentThread().getName() + ",生產隊列" + data + "失敗..");
}
Thread.sleep(1000);
} catch (Exception e) {
}
}
System.out.println(Thread.currentThread().getName() + ",生產者線程停止...");
}
public void stop() {
this.FLAG = false;
}
}
消費者
class ConsumerThread implements Runnable {
private volatile boolean FLAG = true;
private BlockingQueue<String> blockingQueue;
public ConsumerThread(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "消費者開始啓動....");
while (FLAG) {
try {
String data = blockingQueue.poll(2, TimeUnit.SECONDS);
if (data == null || data == "") {
FLAG = false;
System.out.println("消費者超過2秒時間未獲取到消息.");
return;
}
System.out.println("消費者獲取到隊列信息成功,data:" + data);
} catch (Exception e) {
// TODO: handle exception
}
}
}
}
@Test
public void proDucerAndConsumer() throws Exception {
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(3);
ProducerThread producerThread = new ProducerThread(blockingQueue);
ConsumerThread consumerThread = new ConsumerThread(blockingQueue);
Thread t1 = new Thread(producerThread);
Thread t2 = new Thread(consumerThread);
t1.start();
t2.start();
//10秒後 停止線程..
try {
Thread.sleep(10*1000);
producerThread.stop();
} catch (Exception e) {
// TODO: handle exception
}
}
運行效果