一、技術選型
MQ適用于消息堆積,消費端處理不過來;兩點之間運行生命周期不同情況;
JMS支持5種消息類型,為什么不能用隊列來替代呢?
原因:解耦、針對消息本身做隊列操作、消息持久化
PS:如果消費很快,前端積壓嚴重的話,不建議使用mq,可采用直連的TCP通信,如mina、netty,節省中間mq轉發。
二、常用語法
add
增加一個元索,如果隊列已滿,則拋出一IIIegaISlabEepeplian
異常
remove
移除并返回隊列頭部的元素,如果隊列為空,則拋出一個NoSuchElementException
異常
element
返回隊列頭部的元素,如果隊列為空,則拋出一個NoSuchElementException
異常
offer
添加一個元素并返回true,如果隊列已滿,則返回false
poll
移除并返問隊列頭部的元素,如果隊列為空,則返回null
peek
返回隊列頭部的元素,如果隊列為空,則返回null
put
添加一個元素,如果隊列滿,則阻塞
take
移除并返回隊列頭部的元素,如果隊列為空,則阻塞
remove
、element
、offer
、poll
、peek
其實是屬于Queue
接口。
阻塞隊列的操作可以根據它們的響應方式分為以下三類:
(1) add
、remove
和element
操作在你試圖為一個已滿的隊列增加元素或從空隊列取得元素時 拋出異常。
(2) 在多線程程序中,隊列在任何時間都可能變成滿的或空的,所以你可能想使用offer
、poll
、peek
方法。這些方法在無法完成任務時只是給出一個出錯示而不會拋出異常。
注意:poll
和peek
方法出錯進返回null
。因此,向隊列中插入null
值是不合法的。
(3) put
:把Object
加到BlockingQueue
里,如果BlockingQueue
沒有空間,則調用此方法的線程被阻斷,直接有空間再繼續。take
:取走BlockingQueue
里排在首位的對象,若BlockingQueue
為空,阻斷進入等待狀態直到BlockingQueue
有新的數據被加入。
三、并發Queue
在并發隊列上JDK提供了兩套實現,一個是以ConcurrentLinkedQueue為代表的高性能隊列,一個是以BlockingQueue接口為代表的阻塞隊列,都繼承自Queue。
1、ConcurrentLinkedQueue
是一個適用高并發場景下的隊列,通過無鎖的方式,實現了高并發狀態下的高性能,性能好于BlockingQueue,是一個基于鏈接節點的無界線程安全隊列。
該隊列的元素先進先出,頭是先進的,尾是最近加入的,隊列不允許為Null。
重要方法:
add()
、offer()
都是加入元素的方法,無任何區別。
poll()
、peek()
都是取頭元素節點,前者會刪除元素,后者不會。
2、ArrayBlockingQueue
基于數組的阻塞隊列實現,在ArrayBlockingQueue內部,維護一個定長數組,以便緩存隊列中的數據對象,內部沒有實現讀寫分離,意味著生產者和消費者不能完全并行,長度是需要定義的,可以指定先進先出或者先進后出,也叫有界隊列。
/**有界隊列**/
package demo3;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class UserQueue {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> array = new ArrayBlockingQueue<String>(5); // 必須傳一個長度
array.put("a");
array.put("b");
array.add("c");
array.add("d");
array.add("e");
array.add("f");
//System.out.println(array.offer("a", 3, TimeUnit.SECONDS));// 阻塞式
}
}
3、LinkedBlockingQueue
基于鏈表的阻塞隊列,同ArrayBlockingQueue
類似,其內部也維持著一個數據緩沖隊列(該隊列是一個鏈表構成),LinkedBlockingQueue
之所以能夠高效地處理并發數據,是因為內部實現采用了讀寫分離鎖,從而實現生產者和消費者操作的完全并行,是一個無界隊列。
/**無界隊列**/
LinkedBlockingQueue<String> link = new LinkedBlockingQueue<String>(); // 可以不傳,也可以傳,傳則定死;不傳則無界
link.put("a");
link.put("b");
link.add("c");
link.add("d");
link.add("e");
link.add("f");
for (Iterator iterator = link.iterator(); iterator.hasNext();) {
String string = (String) iterator.next();
System.out.println(string);
}
//但如果初始化長度的話,也會有容量限制
4、SynchronousQueue
一種沒有緩沖的隊列,生產者生產的數據直接會被消費者獲取并消費。
/**無緩存隊列**/
SynchronousQueue<String> queue = new SynchronousQueue<String>();
queue.add("a");
/**由于沒有任何容量,直接拋異常**/
Exception in thread "main" java.lang.IllegalStateException: Queue full
at java.util.AbstractQueue.add(AbstractQueue.java:98)
at demo3.UserQueue.main(UserQueue.java:31)
/**可以調用add方法,但并不代表隊列中加元素了,先take再add,直接扔給前面阻塞在take的線程**/
final SynchronousQueue<String> q = new SynchronousQueue<String>();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(q.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
q.add("adsfa");
}
});
t2.start();
5、PriorityBlockingQueue
基于優先級的阻塞隊列(優先級的判斷通過構造函數傳入的Comparator
對象來決定,也就是說傳入隊列的對象必須實現Comparable
接口),不遵循先進先出,在實現PriorityBlockingQueue
時,內部控制線程同步的鎖采用的是公平鎖,它是一個無界隊列。
不是加一個元素時就進行排序,而是調用take/poll
方法時才進行將優先級最高的拿出來。
package demo3;
public class Task implements Comparable<Task> {
private int id;
/**
* @return the id
*/
public int getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(int id) {
this.id = id;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
private String name;
@Override
public int compareTo(Task task) {
return this.id > task.id ? 1 : (this.id < task.id ? -1 : 0);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "Task [id=" + id + ", name=" + name + "]";
}
}
package demo3;
import java.util.Iterator;
import java.util.concurrent.PriorityBlockingQueue;
public class UsePriorityBlockingQueue {
public static void main(String[] args) throws InterruptedException {
PriorityBlockingQueue<Task> q = new PriorityBlockingQueue<Task>();
Task t1 = new Task();
t1.setId(3);
t1.setName("任務1");
Task t2 = new Task();
t2.setId(6);
t2.setName("任務2");
Task t3 = new Task();
t3.setId(1);
t3.setName("任務3");
q.add(t1);
q.add(t2);
q.add(t3);
System.out.println(q);
for (Iterator iterator = q.iterator();iterator.hasNext();){
Task task = (Task)iterator.next();
System.out.println(task.getName());
}
System.out.println(q.poll());
System.out.println(q.poll());
System.out.println(q.poll());
}
}
6、 DelayQueue
帶有延遲時間的Queue
,其中的元素只有當其指定的延遲時間到了,才能夠從隊列中獲取到該元素。DelayQueue
中的元素必須實現Delay
接口,DelayQueue
是一個沒有大小限制的隊列,應用場景很多,比如對緩存超時的數據進行移除、任務超時處理、空閑連接的關閉等。
未超時肯定會阻塞。
四、場景
應用非常繁忙,并發量非常大,應用承載量1000個任務,單核,一個線程,類似于馬路上的早晚高峰,采用ArrayBlockingQueue有界隊列,做容量內存限制,超過的話,給予相應的拒絕措施。
平穩期,采用無界隊列,車流量不是很大,可以放心使用LinkedBlockingQueue,能保證數據的實時性。
夜半時分,不需要存儲到隊列中了(浪費空間),數據量非常少,采用虛擬的隊列(無容量)SynchronousQueue隊列,直接提交給線程,效率更高。