併發集合
數據結構是編程中的基本元素,幾乎每一個程序都使用一種或多種數據結構來存儲和管理數據。java api提供了包含接口、類和算法的java集合框架,它實現了可用在程序中的大量數據結構。
當須要在併發程序中使用數據集合時,必需要謹慎地選擇相應的實現方式。大多數集合類不能直接用於併發應用,由於它們沒有對自己數據的併發訪問進行控制。
若是一些併發任務共享了一個不適用於併發任務的數據結構,將會遇到數據不一致的錯誤,並將影響程序的準確運行。這類數據結構的一個例子是ArrayList類。
java提供了一些能夠用於併發程序中的數據集合,它們不會引發任何問題。通常來講,java提供了兩類適用於併發應用的集合:
1.阻塞式集合(Blocking Collection):這類集合包括添加和移除數據的方法。當集合已滿或者爲空時,被調用的添加或移除方法就不能當即被執行,那麼調用這個方法的線程將被阻塞,一直到該方法能夠被成功執行。
2.非阻塞式集合(Non-Blocking Collection):這類集合也包括添加和移除數據的方法。若是方法不能當即被執行,則返回null或者拋出異常,可是調用這個方法的線程不會被阻塞。
併發應用中經常使用的java集合類:
1.非阻塞式列表對應的實現類:ConcurrentLinkedDeque類
2.阻塞式列表對應的實現類:LinkedBlockingDeque類
3.用於數據生成或消費的阻塞式列表對應的實現類:LinkedTransferQueue類
4.按優先級排序列表元素的阻塞式列表對應的實現類:PriorityBlockingQueue類
5.帶有延遲列表元素的阻塞式列表對應的實現類:DelayQueue類
6.非阻塞式可遍歷映射對應的實現類:ConcurrentSkipListMap類
7.隨機數字對應的實現類:ThreadLocalRandom類
8.原子變量對應的實現類:AtomicLong和AtomicIntegerArray類
1、ConcurrentLinkedDeque類提供的經常使用方法:
1.getFirst()和getLast():分別返回列表中的第一個和最後一個元素,返回的元素不會從列表中移除。若是列表爲空,這兩個方法拋出NoSuchElementExcpetion異常。
2.peek()、peekFirst()和peekLast():分別返回列表中第一個和最後一個元素,返回的元素不會從列表中移除。若是列表爲空,這些方法返回null。
3.remove(), removeFirst(), removeLast():這些方法返回列表的第一個和最後一個元素。他們從列表中移除返回的元素。若是列表是空的,這些方法拋出一個 NoSuchElementException例外。
4.pollFirst()和pollLast():pollFirst()方法返回和刪除列表的第一個元素和pollLast()方法返回和刪除最後一個元素的列表。若是列表爲空,這些方法返回一個null值。
5.size():該方法返回的值可能不是真實的,尤爲當有線程在添數據或者移除數據時。這個方法須要遍歷整個列表來計算元素數量,而遍歷過的數據可能已經改變。僅當沒有任何線程修改列表時,才能保證返回的結果是準確的。
2、LinkedBlockingDeque類提供的經常使用方法:
1.takeFirst和takeLast():分別返回列表中第一個和最後一個元素,返回的元素會從列表中移除。若是列表爲空,調用方法的線程將被阻塞直到列表中有可用的元素出現。
2.getFirst()和getLast():分別返回列表中第一個和最後一個元素,返回的元素不會從列表中移除。若是列表爲空,則拋出NoSuchElementException異常。
3.peek()、peekFirst()和peekLast():分別返回列表中第一個和最後一個元素,返回的元素不會從列表中移除。若是列表爲空,返回null。
4.poll()、pollFirst()和pollLast():分別返回列表中第一個和最後一個元素,返回的元素將會從列表中移除。若是列表爲空,返回null。
5.add()、addFirst()和addLast():分別將元素 添加到列表中第一位和最後一位。若是列表已經滿了,這些方法將拋出ILLegalStateException異常。
3、PriorityBlockingQueue類
數據結構中的一個經典需求是實現一個有序列表。java引用了PriorityBlockingQueue類來知足這類需求。
全部添加進PriorityBlockingQueue的元素必須實現Comparable接口。這個接口提供了compareTo()方法,它的傳入參數是一個同類型的對象。這樣就有了兩個類型的對象而且相互比較:其中一個是執行這個方法的對象,另外一個是參數傳入的對象。這個方法必須返回一個數字值,若是當前對象小於參數傳入的對象,那麼返回一個小於0的值;若是當前對象大於參數傳入的對象,那麼返回一個大於0的值;若是兩個對象相等就是返回0.
當插入元素時,PriorityBlockingQueue使用compareTo()方法來決定插入元素的位置。元素越大越靠後。
PriorityBlockingQueue的另外一個重要的特性是:它是阻塞式數據結構。當它的方法被調用而且不能當即執行時,調用這個方法的線程將被阻塞直到方法執行成功。
PriorityBlockingQueue類提供的經常使用方法:
1.clear():移除隊列中的全部元素
2.take():返回隊列中的第一個元素並將其移除。若是隊列爲空,線程阻塞直到隊列中有可用的元素。
3.put(E e):E是PriorityBlockingQueue的泛型參數,表示傳入參數的類型。這個方法把參數對應的元素插入到隊列中。
4.peek():返回隊列中的第一個元素,但不將其移除。
4、DelayQueue類
java api提供了一種用於併發應用的有趣的數據結構,即DelayQueue類。這個類能夠存放帶有激活日期的元素。當調用方法從隊列中返回或提取元素時,將來的元素日期將被忽略。這些元素對於這些方法是不可見的。
爲了具備條用行爲,存放到DelayQueue類中的元素必須繼承Delayed接口。delayed接口使對象成爲延遲對象,它使存放在DelayQueue類中的對象具備了激活日期,即到激活日期的時間。該接口強制執行下列兩個方法:
1.compareTo(Delayed o):Delayed接口繼承了Comparable接口,所以有了這個方法。若是當前對象的延遲值小於參數對象的值,將返回一個小於0的值;若是當前對象的延遲值大於參數對象的值,將返回一個大於0的值;若是二者的延遲值相等則返回0.
2.getDelay(TimeUnit unit):這個方法返回到激活日期的剩餘時間,單位由單位參數指定。
DelayQueue類提供的經常使用方法:
1.clear():移除隊列中的全部元素。
2.offer(E e):E是DelayQueue的泛型參數,表示傳入參數的類型。這個方法把參數對應的元素插入到隊列中。
3.peek():返回隊列中的第一個元素,但不將其移除。
4.take():返回隊列中的第一個元素,並將其移除。若是隊列爲空,線程將被阻塞直到隊列中有可用的元素。
5、ConcurrentSkipListMap類
java api提供了一種用於併發應用程序中的有趣數據結構,即ConcurrentNavigableMap接口及其實現類。實現這個接口的類以以下兩部分存放元素:
1.一個鍵值(Key),它是元素的標識而且是惟一的。
2.元素其餘部分數據。
每個組成部分必須在不一樣的類中實現。
java api也提供了一個實現ConcurrentSkipListMap接口的類,ConcurrentSkipListMap接口實現了與ConcurrentNavigableMap接口有相同行爲的一個非阻塞式列表。從內部實現機制來說,它使用了一個Skip List來存放數據。Skip List是基於併發列表的數據結構,效率與二叉樹相近。
當插入元素到映射中時,ConcurrentSkipListMap接口類使用鍵值來排序全部元素。除了提供返回一個具體元素的方法外,這個類也提供獲取子映射的方法。
ConcurrentSkipListMap類提供的經常使用方法:
1.headMap(K toKey):K是在ConcurrentSkipListMap對象的 泛型參數裏用到的鍵。這個方法返回映射中全部鍵值小於參數值toKey的子映射。
2.tailMap(K fromKey):K是在ConcurrentSkipListMap對象的 泛型參數裏用到的鍵。這個方法返回映射中全部鍵值大於參數值fromKey的子映射。
3.putIfAbsent(K key,V value):若是映射中不存在鍵key,那麼就將key和value保存到映射中。
4.pollLastEntry():返回並移除映射中的最後一個Map.Entry對象。
5.replace(K key,V value):若是映射中已經存在鍵key,則用參數中的value替換現有的值。
6、ThreadLocalRandom類
java api提供了一個特殊類用以在併發程序中生成僞隨機數,即ThreadLocalRandom類。它是線程本地變量。每一個生成隨機數的線程都有一個不一樣的生成器,可是都在同一個類中被管理,對程序員來說是透明的。
相比於使用共享的Random對象爲全部線程生成隨機數,這種機制具備更好的性能。
7、AtomicLong類
原子變量(Atomic Variable)是從java5開始引入的,它提供了單個變量上的原子操做。在編譯程序時,java代碼中的每一個變量、每一個操做都將被轉換成機器能夠理解的指令。
例如,當給一個變量賦值時,在java代碼中只使用一個指令,可是編譯這個程序時,指令被轉換成JVM語言中的不一樣指令。當多個線程共享同一個變量時,就會發生數據不一致的錯誤。
爲了不這類錯誤,java引入了原子變量。當一個線程在對原子變量操做時,若是其餘線程也試圖對同一原子變量執行操做,原子變量的實現類提供了一套機制來檢查操做是否在一步內完成。通常來講,這個操做先獲取變量值,而後在本地改變變量的值,而後試圖用這個改變的值去替換以前的值。若是以前的值沒有被其餘線程改變,就能夠執行這個替換操做。不然,方法將再次執行這個操做。這種操做稱爲CAS原子操做。
原子變量不使用鎖或其餘同步機制來保護對其值的併發訪問。全部操做都是基於CAS原子操做的。它保證了多線程在同一時間操做一個原子變量而不會產生數據不一致的錯誤,而且它的性能優於使用同步機制保護的普通變量。
8、AtomicIntegerArray類
當實現一個併發應用時,將不可避免地會有多線程共享一個或多個對象的現象,爲了不數據不一致錯誤,須要使用同步機制來保護對這些共享屬性的訪問。可是這些同步機制存在下列問題。
1.死鎖:一個線程被阻塞,而且試圖得到的鎖正被其餘線程使用,但其餘線程永遠不會釋放這個鎖。這種狀況使得應用不會繼續執行,而且永遠不會結束。
2.即便只有一個線程訪問共享對象,它仍然須要執行必須的代碼來獲取和釋放鎖。
針對這種狀況,爲了提供更優的性能,java因而引入了比較和交換操做。這個操做使用一下三步修改變量的值:
1.取得變量值,即變量的舊值。
2.在一個臨時變量中修改變量值,即變量的新值。
3.若是上面得到的變量舊值與當前變量值相等,就用新值替換舊值。若是已有其餘線程修改了這個變量的值,上面得到的變量的舊值就可能與當前變量值不一樣。
採用比較和交換機制不須要使用同步機制,不只能夠避免死鎖而且性能更好。
java在原子變量中實現了這種機制。這些變量提供了實現比較和交換操做的compareAndSet()方法,其餘方法也基於它展開。
java也引入了原子數組(Atomic Array)提供對integer或long數字數組的原子操做。java