Java併發隊列
在併發隊列上JDK提供了兩套實現:
一個是以ConcurrentLinkedQueue爲代表的高性能隊列;
一個是以BlockingQueue接口爲代表的阻塞隊列;
無論哪種都繼承自Queue。
一、ConcurrentLinkedQueue
定義
ConcurrentLinkedQueue : 是一個適用於高併發場景下的隊列,通過無鎖的方式,實現了高併發狀態下的高性能,通常ConcurrentLinkedQueue性能好於BlockingQueue。
它是一個基於鏈接節點的無界線程安全隊列。該隊列的元素遵循先進先出的原則。
頭是最先加入的,尾是最近加入的,該隊列不允許null元素。
ConcurrentLinkedQueue重要方法:
add 和offer() :都是加入元素的方法(在ConcurrentLinkedQueue中這倆個方法沒有任何區別)
poll() 和peek() :都是取頭元素節點,區別在於前者會刪除元素,後者不會。
代碼示例:
ConcurrentLinkedQueue q = new ConcurrentLinkedQueue();
q.offer("張三");
q.offer("李四");
q.offer("王五");
q.offer("趙六");
q.offer("大聖");
//從頭獲取元素,刪除該元素
System.out.println(q.poll());
//從頭獲取元素,不刪除該元素
System.out.println(q.peek());
//獲取總長度
System.out.println(q.size());
二、BlockingQueue
定義:
阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:
1、在隊列爲空時,獲取元素的線程會等待隊列變爲非空。
2、當隊列滿時,存儲元素的線程會等待隊列可用。
阻塞隊列是線程安全的。
用途:
阻塞隊列常用於生產者和消費者的場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器裏拿元素。
1、ArrayBlockingQueue
重要方法:
具體看在線文檔:jdk在線中文文檔
add(E e)
將指定的元素插入到此隊列的尾部(如果立即可行且不會超過該隊列的容量),在成功時返回 true,如果此隊列已滿,則拋出IllegalStateException;
offer(E e)
將指定的元素插入到此隊列的尾部(如果立即可行且不會超過該隊列的容量),在成功時返回 true,如果此隊列已滿,則返回 false。
offer(E e, long timeout, TimeUnit unit)
將指定的元素插入此隊列的尾部,如果該隊列已滿,則在到達指定的等待時間之前等待可用的空間。
定義:
ArrayBlockingQueue是一個有邊界的阻塞隊列,它的內部實現是一個數組。
有邊界的意思是它的容量是有限的,我們必須在其初始化的時候指定它的容量大小,容量大小一旦指定就不可改變。
ArrayBlockingQueue是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。
下面是一個初始化和使用ArrayBlockingQueue的例子:
//初始化3個隊列
ArrayBlockingQueue array = new ArrayBlockingQueue(3);
array.add("張三");
array.add("李四");
array.add("大聖");
// 添加阻塞隊列
boolean a=array.offer("王五",1, TimeUnit.SECONDS);
System.out.println(a);
//運行結果:false
2、LinkedBlockingQueue
定義:
LinkedBlockingQueue阻塞隊列大小的配置是可選的,
如果我們初始化時指定一個大小,它就是有邊界的,如果不指定,它就是無邊界的。
說是無邊界,其實是採用了默認大小爲Integer.MAX_VALUE的容量 。它的內部實現是一個鏈表。
和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。
下面是一個初始化和使LinkedBlockingQueue的例子:
//初始化
LinkedBlockingQueue lbq = new LinkedBlockingQueue(3);
lbq.add("張三");
lbq.add("李四");
lbq.add("李四");
System.out.println(lbq.size());
//運行結果:3
3、PriorityBlockingQueue
定義:
PriorityBlockingQueue是一個沒有邊界的隊列,它的排序規則和 java.util.PriorityQueue一樣。需要注意,PriorityBlockingQueue中允許插入null對象。
所有插入PriorityBlockingQueue的對象必須實現 java.lang.Comparable接口,隊列優先級的排序規則就是按照我們對這個接口的實現來定義的。
另外,我們可以從PriorityBlockingQueue獲得一個迭代器Iterator,但這個迭代器並不保證按照優先級順序進行迭代。
4、SynchronousQueue
定義:
SynchronousQueue隊列內部僅允許容納一個元素。當一個線程插入一個元素後會被阻塞,除非這個元素被另一個線程消費。
三、使用BlockingQueue模擬生產者與消費者
代碼示例
生產者:
public class ProducerThread implements Runnable {
private BlockingQueue queue;
private volatile boolean flag = true;
private static AtomicInteger count = new AtomicInteger();
public ProducerThread(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
System.out.println("生產線程啓動...");
while (flag) {
System.out.println("正在生產數據....");
String data = count.incrementAndGet()+"";
// 將數據存入隊列中
boolean offer = queue.offer(data, 2, TimeUnit.SECONDS);
if (offer) {
System.out.println("生產者,存入" + data + "到隊列中,成功.");
} else {
System.out.println("生產者,存入" + data + "到隊列中,失敗.");
}
Thread.sleep(1000);
}
} catch (Exception e) {
} finally {
System.out.println("生產者退出線程");
}
}
public void stopThread() {
this.flag = false;
}
}
消費者:
class ConsumerThread implements Runnable {
private BlockingQueue<String> queue;
private volatile boolean flag = true;
public ConsumerThread(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
System.out.println("消費線程啓動...");
try {
while (flag) {
System.out.println("消費者,正在從隊列中獲取數據..");
String data = queue.poll(2, TimeUnit.SECONDS);
if (data != null) {
System.out.println("消費者,拿到隊列中的數據data:" + data);
Thread.sleep(1000);
} else {
System.out.println("消費者,超過2秒未獲取到數據..");
flag = false;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("消費者退出線程...");
}
}
}
運行:
public class ProducerAndConsumer {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new LinkedBlockingQueue<String>(10);
ProducerThread producerThread1 = new ProducerThread(queue);
ProducerThread producerThread2 = new ProducerThread(queue);
ConsumerThread consumerThread1 = new ConsumerThread(queue);
Thread t1 = new Thread(producerThread1);
Thread t2 = new Thread(producerThread2);
Thread c1 = new Thread(consumerThread1);
t1.start();
t2.start();
c1.start();
// 執行2s後,生產者不再生產
Thread.sleep(2* 1000);
producerThread1.stopThread();
producerThread2.stopThread();
}
}
運行結果: