聊聊Java裡常用的併發集合
前言
在我們的程式開發過程中,如果涉及到多執行緒環境,那麼對於集合框架的使用就必須更加謹慎了,因為大部分的集合類在不施加額外控制的情況下直接在併發環境中直接使用可能會出現資料不一致的問題,所以為了解決這個潛在的問題,我們要麼在自己的業務邏輯中加上一些額外的控制,例如鎖
,或者我們直接使用Java提供的可在併發環境中使用的集合類,這是一個簡便而且高效的方法。那麼我們下面就來了解下Java提供了哪些“神器”可以讓我們安全的使用集合。
正文
非阻塞式安全列表 - ConcurrentLinkedDeque
ConcurrentLinkedDeque可以在併發環境中直接使用,所謂的非阻塞,就是當列表為空的時候,我們還繼續從列表中取資料的話,它會直接返回null或者丟擲異常。下面列出來一些常用的方法。
peekFirst()
、peekLast()
:返回列表中首位跟末尾元素,如果列表為空則返回null。返回的元素不從列表中刪除。getFirst()
、getLast()
:返回列表中首位跟末尾元素,如果列表為空則丟擲NoSuchElementExceotion
異常。返回的元素不從列表中刪除。removeFirst()
、removeLast()
:返回列表中首位跟末尾元素,如果列表為空則丟擲NoSuchElementExceotion
異常。【返回的元素會從列表中刪除】。
阻塞式安全列表 - LinkedBlockingDeque
LinkedBlockingDeque是一個阻塞式的執行緒安全列表,它跟 ConcurrentLinkedDeque最大的區別就是,當列表中元素滿了或者為空的時候,我們對該列表的操作不會立即返回,而是阻塞當前操作,直到該操作可以執行時才返回。我們對比著上面ConcurrentLinkedDeque的常用方法,來看下LinkedBlockingDeque會有哪些不一致的地方呢?
put()
:插入元素至列表中,當表中元素已滿的時候,該操作將會被阻塞,直到表中存在空餘空間。take()
: 從列表中獲取元素,當列表為空,該操作會被阻塞,直到列表不為空。peekFirst()
、peekLast()
:返回列表中首位跟末尾元素,如果列表為空則返回null。返回的元素不從列表中刪除。getFirst()
、getLast()
:返回列表中首位跟末尾元素,如果列表為空則丟擲NoSuchElementExceotion
異常。返回的元素不從列表中刪除。addFirst()
、addLast()
:將元素新增至首位跟末尾,如果列表已滿,則會丟擲IllegalStateException
可以看出不管是從獲取還是插入元素,都多了不少“花樣”,其差別就在於是否阻塞,不滿足條件是否返回null,不滿足條件是否拋異常這幾個方面來區分。
優先順序排序阻塞式安全列表 - PriorityBlockingQueue
相信大家都寫過把某個列表元素按照特定的規則來排序之類的程式碼,在PriorityBlockingQueue
中,存放進去的元素必須要實現Comparable介面。在這個介面中,有一個compareTo()
方法,當執行該方法的物件跟引數傳入的物件進行比較的時候,這個方法會返回一個數字值,如果值小於0,則當前物件小於引數傳入物件。大於0則相反,等於0就表示兩個物件相等。
public class DemoObj implements Comparable<DemoObj> { private int priority; @Override public int compareTo(DemoObj do){ if(this.getPriority() > do.getPriority()){ return -1; }else if(this.getPriority() < do.getPriority()){ return 1; } return 0; } //省略getset ... } //==== use =================== PriorityBlockingQueue<DemoObj> queue = new PriorityBlockingQueue()<>; queue.put(DemoObj); queue.peek();
其常用方法跟上面提到的類基本都差不多大家可以動手實現一下,簡單對比的話,可以說是LinkedBlockingDeque的增強版,多了元素排序功能。
延遲元素執行緒安全列表 - DelayQueue
DelayQueue 裡面存放著帶有日期的元素,當我們從列表獲取資料的時候,未到時間的元素將會被忽略。因此,存放進來的元素必須實現Delayed介面,使之成為一個延遲物件。
/** * compareTo方法與getDelay方法需排序一致 */ class Order implements Delayed{ private String name ; private long start = System.currentTimeMillis(); private long time ; public MyDelayedTask(String name,long time) { this.name = name; this.time = time; } /** * 需要實現的介面,獲得延遲時間 用過期時間-當前時間 * @param unit * @return */ @Override public long getDelay(TimeUnit unit) { return unit.convert((start+time) - System.currentTimeMillis(),TimeUnit.MILLISECONDS); } /** * 延遲佇列內部排序 當前物件延遲時間 - 入參物件的延遲時間 * @param o * @return */ @Override public int compareTo(Delayed o) { Order o1 = (Order) o; return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o1.getDelay(TimeUnit.MILLISECONDS)); } }
使用方式如下
private static DelayQueue delayQueue = new DelayQueue(); public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { @Override public void run() { delayQueue.offer(new Order("t3000",3000)); delayQueue.offer(new Order("t4000",4000)); delayQueue.offer(new Order("t2000",2000)); delayQueue.offer(new Order("t6000",6000)); delayQueue.offer(new Order("t1000",1000)); } }).start(); while (true) { Delayed take = delayQueue.take(); } }
關於結果的輸出大家可以動手嘗試一下~
結語
這裡僅僅是介紹了幾種常用的併發集合,其目的主要是讓大家對這些集合有一個直觀的認識,在使用的時候可以思考下自己的場景用哪種更合適,如果當前介紹的類沒合適的,那麼是否還有其他併發集合會更有用呢?這裡就當做拋磚引玉吧,有興趣的朋友可以多去了解一下相關技術,相信你會有不少收穫的。
公眾號博文同步Github倉庫,有興趣的朋友可以幫忙給個Star哦,碼字不易,感謝支援。
https://github.com/PeppaLittlePig/blog-wechat
推薦閱讀
《如何優化程式碼中大量的if/else,switch/case?》
《如何提高使用Java反射的效率?》
《Java日誌正確使用姿勢》
關注「深夜裡的程式猿」,分享最乾的乾貨