最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java同步容器和并發(fā)容器詳解

 更新時間:2019年06月19日 15:31:40   作者:一入碼坑深似海  
這篇文章主要介紹了Java同步容器和并發(fā)容器詳解,容器是Java基礎類庫中使用頻率最高的一部分,Java集合包中提供了大量的容器類來幫組我們簡化開發(fā),下面小編和大家來一起學習下吧

同步容器

在 Java 中,同步容器主要包括 2 類:

  • Vector、Stack、HashTableCollections 類中提供的靜態(tài)工廠方法創(chuàng)建的類(由 Collections.synchronizedXxxx 等方法)
  • Collections類中提供的靜態(tài)工廠方法創(chuàng)建的類

Vector 實現了 List 接口,Vector 實際上就是一個數組,和 ArrayList 類似,但是Vector 中的方法都是 synchronized 方法,即進行了同步措施。

Stack 也是一個同步容器,它的方法也用 synchronized 進行了同步,它實際上是繼承于 Vector 類。

HashTable 實現了 Map 接口,它和 HashMap 很相似,但是 HashTable 進行了同步處理,而 HashMap 沒有。

同步容器的缺陷

同步容器的同步原理就是在方法上用 synchronized 修飾。那么,這些方法每次只允許一個線程調用執(zhí)行。

性能問題

由于被 synchronized 修飾的方法,每次只允許一個線程執(zhí)行,其他試圖訪問這個方法的線程只能等待。顯然,這種方式比沒有使用 synchronized 的容器性能要差。

安全問題

同步容器真的一定安全嗎?

答案是:未必。同步容器未必真的安全。在做復合操作時,仍然需要加鎖來保護。

常見復合操作如下:

  • 迭代:反復訪問元素,直到遍歷完全部元素;
  • 跳轉:根據指定順序尋找當前元素的下一個(下 n 個)元素;
  • 條件運算:例如若沒有則添加等;

不安全的示例

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public class Test { static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for (int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
for (int i=0;i<vector.size();i++)
vector.remove(i);
}
;
}
;
Thread thread2 = new Thread(){
public void run() {
for (int i=0;i<vector.size();i++)
vector.get(i);
}
;
}
;
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}
</pre>

執(zhí)行時可能會出現數組越界錯誤。

Vector 是線程安全的,為什么還會報這個錯?很簡單,對于 Vector,雖然能保證每一個時刻只能有一個線程訪問它,但是不排除這種可能:
當某個線程在某個時刻執(zhí)行這句時:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
vector.get(i);
</pre>

假若此時 vector 的 size 方法返回的是 10,i 的值為 9

然后另外一個線程執(zhí)行了這句:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">for (int i=0;i<vector.size();i++)
vector.remove(i);
</pre>

將下標為 9 的元素刪除了。

那么通過 get 方法訪問下標為 9 的元素肯定就會出問題了。

安全示例

因此為了保證線程安全,必須在方法調用端做額外的同步措施,如下面所示:

public class Test {
static Vector<Integer> vector = new Vector<Integer>();
public static void main(String[] args) throws InterruptedException {
while(true) {
for (int i=0;i<10;i++)
vector.add(i);
Thread thread1 = new Thread(){
public void run() {
synchronized (Test.class) {
//進行額外的同步
for (int i=0;i<vector.size();i++)
vector.remove(i);
}
}
;
}
;
Thread thread2 = new Thread(){
public void run() {
synchronized (Test.class) {
for (int i=0;i<vector.size();i++)
vector.get(i);
}
}
;
}
;
thread1.start();
thread2.start();
while(Thread.activeCount()>10) {
}
}
}
}

ConcurrentModificationException 異常

在對 Vector 等容器并發(fā)地進行迭代修改時,會報 ConcurrentModificationException 異常,關于這個異常將會在后續(xù)文章中講述。

但是在并發(fā)容器中不會出現這個問題。

并發(fā)容器

JDK 的 java.util.concurrent 包(即 juc)中提供了幾個非常有用的并發(fā)容器。

  • CopyOnWriteArrayList - 線程安全的 ArrayList
  • CopyOnWriteArraySet - 線程安全的 Set,它內部包含了一個 CopyOnWriteArrayList,因此本質上是由 CopyOnWriteArrayList 實現的。
  • ConcurrentSkipListSet - 相當于線程安全的 TreeSet。它是有序的 Set。它由 ConcurrentSkipListMap 實現。
  • ConcurrentHashMap - 線程安全的 HashMap。采用分段鎖實現高效并發(fā)。
  • ConcurrentSkipListMap - 線程安全的有序 Map。使用跳表實現高效并發(fā)。
  • ConcurrentLinkedQueue - 線程安全的無界隊列。底層采用單鏈表。支持 FIFO。
  • ConcurrentLinkedDeque - 線程安全的無界雙端隊列。底層采用雙向鏈表。支持 FIFO 和 FILO。
  • ArrayBlockingQueue - 數組實現的阻塞隊列。
  • LinkedBlockingQueue - 鏈表實現的阻塞隊列。
  • LinkedBlockingDeque - 雙向鏈表實現的雙端阻塞隊列。

ConcurrentHashMap

要點

作用:ConcurrentHashMap 是線程安全的 HashMap。
原理:JDK6 與 JDK7 中,ConcurrentHashMap 采用了分段鎖機制。JDK8 中,摒棄了鎖分段機制,改為利用 CAS 算法。

源碼

JDK7

ConcurrentHashMap 類在 jdk1.7 中的設計,其基本結構如圖所示:

每一個 segment 都是一個 HashEntry<K,V>[] table, table 中的每一個元素本質上都是一個 HashEntry 的單向隊列。比如 table[3]為首節(jié)點,table[3]->next 為節(jié)點 1,之后為節(jié)點 2,依次類推。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable { // 將整個hashmap分成幾個小的map,每個segment都是一個鎖;與hashtable相比,這么設計的目的是對于put, remove等操作,可以減少并發(fā)沖突,對 // 不屬于同一個片段的節(jié)點可以并發(fā)操作,大大提高了性能
final Segment<K,V>[] segments;
// 本質上Segment類就是一個小的hashmap,里面table數組存儲了各個節(jié)點的數據,繼承了ReentrantLock, 可以作為互拆鎖使用
static final class Segment<K,V> extends ReentrantLock implements Serializable {
transient volatile HashEntry<K,V>[] table;
transient int count;
}
// 基本節(jié)點,存儲Key, Value值
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
}
</pre>

JDK8

jdk8 中主要做了 2 方面的改進

  • 取消 segments 字段,直接采用 transient volatile HashEntry<K,V>[] table 保存數據,采用 table 數組元素作為鎖,從而實現了對每一行數據進行加鎖,進一步減少并發(fā)沖突的概率。
  • 將原先 table 數組+單向鏈表的數據結構,變更為 table 數組+單向鏈表+紅黑樹的結構。

對于 hash 表來說,最核心的能力在于將 key hash 之后能均勻的分布在數組中。如果 hash 之后散列的很均勻,那么 table 數組中的每個隊列長度主要為 0 或者 1。

但實際情況并非總是如此理想,雖然 ConcurrentHashMap 類默認的加載因子為 0.75,但是在數據量過大或者運氣不佳的情況下,還是會存在一些隊列長度過長的情況,如果還是采用單向列表方式,那么查詢某個節(jié)點的時間復雜度為 O(n);

因此,對于個數超過 8(默認值)的列表,jdk1.8 中采用了紅黑樹的結構,那么查詢的時間復雜度可以降低到 O(logN),可以改進性能。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">final V putVal(K key, V value, Boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f;
int n, i, fh;
// 如果table為空,初始化;否則,根據hash值計算得到數組索引i,如果tab[i]為空,直接新建節(jié)點Node即可。注:tab[i]實質為鏈表或者紅黑樹的首節(jié)點。
if (tab == null || (n = tab.length) == 0)
tab = initTable(); else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break;
// no lock when adding to empty bin
}
// 如果tab[i]不為空并且hash值為MOVED,說明該鏈表正在進行transfer操作,返回擴容完成后的table。 else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f); else {
V oldVal = null;
// 針對首個節(jié)點進行加鎖操作,而不是segment,進一步減少線程沖突
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 如果在鏈表中找到值為key的節(jié)點e,直接設置e.val = value即可。
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// 如果沒有找到值為key的節(jié)點,直接新建Node并加入鏈表即可。
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 如果首節(jié)點為TreeBin類型,說明為紅黑樹結構,執(zhí)行putTreeVal操作。 else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
// 如果節(jié)點數>=8,那么轉換鏈表結構為紅黑樹結構。
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null) return oldVal;
break;
}
}
}
// 計數增加1,有可能觸發(fā)transfer操作(擴容)。
addCount(1L, binCount);
return null;
}
</pre>

示例

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public class ConcurrentHashMapDemo { public static void main(String[] args) throws InterruptedException { // HashMap 在并發(fā)迭代訪問時會拋出 ConcurrentModificationException 異常 // Map<Integer, Character> map = new HashMap<>();
Map<Integer, Character> map = new ConcurrentHashMap<>();
Thread wthread = new Thread(() -> {
System.out.println("寫操作線程開始執(zhí)行"); for (int i = 0; i < 26; i++) {
map.put(i, (char) ('a' + i));
}
});
Thread rthread = new Thread(() -> {
System.out.println("讀操作線程開始執(zhí)行"); for (Integer key : map.keySet()) {
System.out.println(key + " - " + map.get(key));
}
});
wthread.start();
rthread.start();
Thread.sleep(1000);
}
}</pre>

CopyOnWriteArrayList

要點

作用:CopyOnWrite 字面意思為寫入時復制。CopyOnWriteArrayList 是線程安全的 ArrayList。

原理:

  • 在 CopyOnWriteAarrayList 中,讀操作不同步,因為它們在內部數組的快照上工作,所以多個迭代器可以同時遍歷而不會相互阻塞(1,2,4)。
  • 所有的寫操作都是同步的。他們在備份數組(3)的副本上工作。寫操作完成后,后備陣列將被替換為復制的陣列,并釋放鎖定。支持數組變得易變,所以替換數組的調用是原子(5)。
  • 寫操作后創(chuàng)建的迭代器將能夠看到修改的結構(6,7)。
  • 寫時復制集合返回的迭代器不會拋出 ConcurrentModificationException,因為它們在數組的快照上工作,并且無論后續(xù)的修改(2,4)如何,都會像迭代器創(chuàng)建時那樣完全返回元素。

源碼

重要屬性

  • lock - 執(zhí)行寫時復制操作,需要使用可重入鎖加鎖
  • array - 對象數組,用于存放元素
<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;"> 
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
</pre>

重要方法

添加操作

添加的邏輯很簡單,先將原容器 copy 一份,然后在新副本上執(zhí)行寫操作,之后再切換引用。當然此過程是要加鎖的。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public Boolean add(E e) {
//ReentrantLock加鎖,保證線程安全
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//拷貝原容器,長度為原容器長度加一
Object[] newElements = Arrays.copyOf(elements, len + 1);
//在新副本上執(zhí)行添加操作
newElements[len] = e;
//將原容器引用指向新副本
setArray(newElements);
return true;
}
finally {
//解鎖
lock.unlock();
}
}
</pre>

刪除操作

刪除操作同理,將除要刪除元素之外的其他元素拷貝到新副本中,然后切換引用,將原容器引用指向新副本。同屬寫操作,需要加鎖。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public E remove(int index) {
//加鎖
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0) //如果要刪除的是列表末端數據,拷貝前l(fā)en-1個數據到新副本上,再切換引用
setArray(Arrays.copyOf(elements, len - 1)); else {
//否則,將除要刪除元素之外的其他元素拷貝到新副本中,并切換引用
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
}
finally {
//解鎖
lock.unlock();
}
}
</pre>

讀操作

CopyOnWriteArrayList 的讀操作是不用加鎖的,性能很高。

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
</pre>

示例

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: &quot;Courier New&quot; !important; font-size: 12px !important;">public class CopyOnWriteArrayListDemo { static class ReadTask implements Runnable {
List<String> list;
ReadTask(List<String> list) {
this.list = list;
}
public void run() {
for (String str : list) {
System.out.println(str);
}
}
}
static class WriteTask implements Runnable {
List<String> list;
int index;
WriteTask(List<String> list, int index) {
this.list = list;
this.index = index;
}
public void run() {
list.remove(index);
list.add(index, "write_" + index);
}
}
public void run() {
final int NUM = 10;
// ArrayList 在并發(fā)迭代訪問時會拋出 ConcurrentModificationException 異常 // List<String> list = new ArrayList<>();
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < NUM; i++) {
list.add("main_" + i);
}
ExecutorService executorService = Executors.newFixedThreadPool(NUM);
for (int i = 0; i < NUM; i++) {
executorService.execute(new ReadTask(list));
executorService.execute(new WriteTask(list, i));
}
executorService.shutdown();
}
public static void main(String[] args) {
new CopyOnWriteArrayListDemo().run();
}
}
</pre>

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • Java用正則表達式實現${name}形式的字符串模板實例

    Java用正則表達式實現${name}形式的字符串模板實例

    這篇文章主要給大家介紹了Java如何用正則表達式實現${name}形式的字符串模板,文章給出詳細的實例代碼,對大家的理解和學習會很有幫助,有需要的朋友們下面來一起看看吧。
    2016-12-12
  • Java設計模式之建造者模式(Builder模式)介紹

    Java設計模式之建造者模式(Builder模式)介紹

    這篇文章主要介紹了Java設計模式之建造者模式(Builder模式)介紹,本文講解了為何使用建造者模式、如何使用建造者模式、Builder模式的應用等內容,需要的朋友可以參考下
    2015-03-03
  • HttpClient實現遠程調用

    HttpClient實現遠程調用

    這篇文章主要為大家詳細介紹了HttpClient實現遠程調用的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • Java中的2種集合排序方法介紹

    Java中的2種集合排序方法介紹

    這篇文章主要介紹了Java中的2種集合排序方法介紹,本文直接給出代碼,相關說明請看代碼中的注釋,需要的朋友可以參考下
    2014-10-10
  • Java中類的初始化和實例化區(qū)別詳解

    Java中類的初始化和實例化區(qū)別詳解

    這篇文章主要介紹了Java中類的初始化和實例化區(qū)別詳解,類的初始化<BR>是完成程序執(zhí)行前的準備工作,類的實例化(實例化對象)是指創(chuàng)建一個對象的過程,需要的朋友可以參考下
    2023-08-08
  • RestTemplate實現發(fā)送帶headers的GET請求

    RestTemplate實現發(fā)送帶headers的GET請求

    這篇文章主要介紹了RestTemplate實現發(fā)送帶headers的GET請求,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 3行代碼快速實現Spring Boot Oauth2服務功能

    3行代碼快速實現Spring Boot Oauth2服務功能

    oauthserver是一個基于Spring Boot Oauth2的完整的獨立的Oauth服務器。僅僅需要創(chuàng)建相關數據表,修改數據庫的連接信息,你就可以得到一個Oauth服務器。這篇文章給大家介紹3行代碼快速實現Spring Boot Oauth2服務功能,需要的朋友參考下吧
    2018-04-04
  • 如果你想寫自己的Benchmark框架(推薦)

    如果你想寫自己的Benchmark框架(推薦)

    這篇文章主要介紹了如果你想寫自己的Benchmark框架,本文通過給大家分享八條軍規(guī),幫助大家理解,需要的朋友可以參考下
    2020-07-07
  • Java 解析XML數據的4種方式

    Java 解析XML數據的4種方式

    這篇文章主要介紹了Java 解析XML數據的4種方式,幫助大家更好的用Java處理數據,感興趣的朋友可以了解下
    2020-09-09
  • java 漢諾塔詳解及實現代碼

    java 漢諾塔詳解及實現代碼

    這篇文章主要介紹了java 漢諾塔詳解及實現代碼的相關資料,需要的朋友可以參考下
    2017-04-04

最新評論

万年县| 内乡县| 鱼台县| 咸阳市| 什邡市| 东方市| 来安县| 剑川县| 万荣县| 郯城县| 建水县| 封开县| 河东区| 普兰店市| 安顺市| 吉林市| 子长县| 曲沃县| 满城县| 唐山市| 澎湖县| 新和县| 灵川县| 澄城县| 仪征市| 宜都市| 鲁山县| 丹寨县| 九江县| 赣州市| 曲靖市| 朔州市| 木兰县| 南开区| 白河县| 明光市| 宁陕县| 辽宁省| 华池县| 罗甸县| 西平县|