總結一下在多線程模式下,常用的一些數據結構.
CountDownLatch - 同步工具類
CountDownLatch這個類能夠使一個線程等待其他線程完成各自的工作后再執行。例如,應用程序的主線程希望在負責啟動框架服務的線程已經啟動所有的框架服務之后再執行。或者希望若發生了獲取某個變量,必須等某異步線程做完了才能獲取到, 否則阻塞.
方法
// 初始化
private CountDownLatch mReplySequenceLatch = new CountDownLatch(1);
// 計數-1
mReplySequenceLatch.countDown();
// 等待
mReplySequenceLatch.await();
CountDownLatch正常就用上面幾個方法,一個初始化等待的線程數,然后每個線程運行結束時countDown一下, 在需要阻塞的地方await即可.
我們舉個真實用例,來自Google zxing的源碼.
final class DecodeThread extends Thread {
public static final String BARCODE_BITMAP = "barcode_bitmap";
private final CaptureActivity activity;
private final Hashtable<DecodeHintType,Object> hints;
private Handler handler;
private final CountDownLatch handlerInitLatch;
DecodeThread(CaptureActivity activity,
String characterSet,
ResultPointCallback resultPointCallback) {
this.activity = activity;
handlerInitLatch = new CountDownLatch(1);
hints = new Hashtable<DecodeHintType,Object>();
Vector<BarcodeFormat> formats = new Vector<BarcodeFormat>();
formats.add(BarcodeFormat.QR_CODE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, formats);
if (characterSet != null) {
hints.put(DecodeHintType.CHARACTER_SET, characterSet);
}
hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback);
}
// 若已經countdown過了,則直接return
Handler getHandler() {
try {
handlerInitLatch.await();
} catch (InterruptedException ie) {
// continue?
}
return handler;
}
@Override
public void run() {
Looper.prepare();
handler = new DecodeHandler(activity, hints);
//
handlerInitLatch.countDown();
Looper.loop();
}
}
上述使用CountDownLatch
做到了當該類被實類化之后,直接去getHandler是會阻塞的.只有等到運行了,變量準備好了,獲取方法才會返回,這保證了get肯定能拿到對象,拿不到的情況就在等待.有點類似阻塞單列模式的味道.
BlockingQueue-阻塞隊列
來自concurrent
包, 多線程編程時經常用到,尤其是任務分配,或者生產者消費者這種類型的.
BlockingQueue的核心方法:
- 放入數據:
offer(anObject)
:表示如果可能的話,將anObject加到BlockingQueue里,即如果BlockingQueue可以容納,
則返回true,否則返回false.(本方法不阻塞當前執行方法的線程)
offer(E o, long timeout, TimeUnit unit)
,可以設定等待的時間,如果在指定的時間內,還不能往隊列中
加入BlockingQueue,則返回失敗。
put(anObject)
:把anObject加到BlockingQueue里,如果BlockQueue沒有空間,則調用此方法的線程被阻斷
直到BlockingQueue里面有空間再繼續.
- 獲取數據:
poll(time)
:取走BlockingQueue里排在首位的對象,若不能立即取出,則可以等time參數規定的時間,
取不到時返回null;
poll(long timeout, TimeUnit unit)
:從BlockingQueue取出一個隊首的對象,如果在指定時間內,
隊列一旦有數據可取,則立即返回隊列中的數據。否則知道時間超時還沒有數據可取,返回失敗。
take()
:取走BlockingQueue里排在首位的對象,若BlockingQueue為空,阻斷進入等待狀態直到
BlockingQueue有新的數據被加入;
drainTo()
:一次性從BlockingQueue獲取所有可用的數據對象(還可以指定獲取數據的個數),
通過該方法,可以提升獲取數據效率;不需要多次分批加鎖或釋放鎖。
常用:
ArrayBlockingQueue
LinkedBlockingQueue
于鏈表的阻塞隊列,同ArrayListBlockingQueue類似,其內部也維持著一個數據緩沖隊列(該隊列由一個鏈表構成),當生產者往隊列中放入一個數據時,隊列會從生產者手中獲取數據,并緩存在隊列內部,而生產者立即返回;只有當隊列緩沖區達到最大值緩存容量時(LinkedBlockingQueue可以通過構造函數指定該值),才會阻塞生產者隊列PriorityBlockingQueue
基于優先級的阻塞隊列(優先級的判斷通過構造函數傳入的Compator對象來決定),但需要注意的是PriorityBlockingQueue并不會阻塞數據生產者,而只會在沒有可消費的數據時,阻塞數據的消費者。因此使用的時候要特別注意,生產者生產數據的速度絕對不能快于消費者消費數據的速度,否則時間一長,會最終耗盡所有的可用堆內存空間。
ConcurrentLinkedQueue-非阻塞隊列
BlockingQueue系列對應的是主要是同步操作,是阻塞的,而ConcurrentLinkedQueue是非阻塞的,Queue中元素按FIFO原則進行排序.采用CAS操作,來保證元素的一致性。我們可以根據是否需要阻塞選擇使用哪個數據結構.
ConcurrentLinkedQueue應該注意的地方:
- 并不是使用ConcurrentLinkedQueue類之后意味著不需要自己進行任何同步或加鎖操作,查了下資料,
如果直接使用它提供的函數,比如:queue.add(obj); 或者 queue.poll(obj);,這樣我們自己不需要做任何同步。
但如果是非原子操作,比如:
if(!queue.isEmpty()) {
queue.poll(obj);
}
我們很難保證,在調用了isEmpty()之后,poll()之前,這個queue沒有被其他線程修改。
所以對于這種情況,我們還是需要自己同步:
synchronized(queue) {
if(!queue.isEmpty()) {
queue.poll(obj);
}
}
當然,如果是可以接受的臟讀同樣可以不用加synchronized
- 判斷是否還有元素時,ConcurrentLinkedQueue的API原來.size()是要遍歷一遍集合的,比較慢,所以盡量要避免用size而改用isEmpty().
ConcurrentHashMap
一個經常被使用的數據結構,因為HashMap的線程不安全,以及Hashtable的低效,,相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在線程安全的基礎上提供了更好的寫并發能力,但同時降低了對讀一致性的要求.
ConcurrentHashMap代碼中可以看出,它引入了一個“分段鎖”的概念,具體可以理解為把一個大的Map拆分成N個小的HashTable,根據key.hashCode()來決定把key放到哪個HashTable中。
在ConcurrentHashMap中,就是把Map分成了N個Segment,put和get的時候,都是現根據key.hashCode()算出放到哪個Segment中.默認是16個段.
concurrentHashmap維護一個segment數組,將元素分成若干段(第一次hash)
/**
* The segments, each of which is a specialized hash table.
*/
final Segment<K,V>[] segments;
segments的每一個segment維護一個鏈表數組
在大并發的情況下,只會影響某一個segment的rehash而其他segment不會受到影響
Collections.synchronized類方法
Collection c = Collections.synchronizedCollection(new ArrayList());
List list = Collections.synchronizedList(new ArrayList());
Set set = Collections.synchronizedSet(new HashSet());
Map map = Collections.synchronizedMap(new HashMap());
返回一個線程安全的集合類.
不過需要注意的是, 這些類的同步也只是靠其內部的一個鎖來控制的,所以若有需要連續的鎖控制的地方,我們還是得自己進行同步控制.說實話這個方法我不常用,因為任何能用到它的地方好像都有方案代替.
參考文章:Collections.synchronizedList()不同鎖造成的陷阱
ThreadLocal<T> 為每個線程創建一個單獨的變量副本,提供了保持對象的方法和避免參數傳遞的復雜性
以前的Java版本是通過, Thread的ThreadLocal.Values變量來做的,保持每個線程有獨立的變量.
jdk1.7開始,改為ThreadLocalMap.即通過為每個線程實現ThreadLocalMap來實現每個線程有獨立變量.
- void set(Object value)設置當前線程的線程局部變量的值。
- public Object get()該方法返回當前線程所對應的線程局部變量。
- public void remove()將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量并不是必須的操作,但它可以加快內存回收的速度。
- protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,并且僅執行1次,ThreadLocal中的缺省實現直接返回一個null。
本文作者:Anderson/Jerey_Jobs
博客地址 : http://jerey.cn/
簡書地址 : Anderson大碼渣
github地址 : https://github.com/Jerey-Jobs