なぜjuc下のコレクションは、スレッドセーフであります

はじめに1. JUC

  5.0ではJavaの提供  java.util.concurrent(JUCをいう。)のパッケージ、スレッドプール、非同期IO、および軽量タスクを含むカスタム・サブシステムを定義するために使用されるスレッドに似たこのパッケージの並行プログラミングは非常に一般的に使用されるツールは、増加をフレーム;デザインも実現コレクション、マルチスレッドコンテキストのために用意されています。

2.同時コンテナ

  我々は、すべてのJavaパッケージでコレクションのほとんどは、スレッドセーフではないことを知っている、とベクトルは、スタック、ハッシュテーブルは、スレッドセーフで、スレッドの安全性は、彼らの同期、低効率に依存することです。Javaクラスのセットは、同期パケットを取得することができますがメソッドコレクションツールで対応しているが、これらのクラスの同時同期効率が非常に高いではありませんより良い高同時タスクをサポートするために、同時マスターダグ・リーは、同時ハイクラスJUC(java.util.concurrentの)パッケージをネジ付きパッケージのサポートJavaクラスの対応するセットを追加します。

例えば、ArrayListに対応するクラスは、高い並行性CopyOnWriteArrayListと、HashMapの対応するクラスであり、高い並行性のConcurrentHashMap、等です。

  ときAPIインタフェース、フレームワークの「Javaの収集袋」の一貫性を維持するために、JUCパッケージ「Javaの収集袋」高に対応する並行クラスを追加。例えば、CopyOnWriteArrayListとは、Listインタフェースの「Javaのコレクションパッケージ」を達成、ConcurrentHashMapのはAbstractMapのカテゴリの「Javaのコレクションパッケージ」を継承し、そう、そう、私たちは理解しやすいです。

 

そして、セットリスト

JUCコレクションパッケージの実装クラスとセットリストは:CopyOnWriteArrayListと、CopyOnWriteArraySetとConcurrentSkipListSetのを。CopyOnWriteArrayListととCopyOnWriteArraySetフレーム下記のように:
ここに画像を挿入説明

(01)は、Listインタフェースを実装する同等のスレッドセーフのArrayListを、CopyOnWriteArrayListと。CopyOnWriteArrayListとは、高い同時実行をサポートしています。
(02)は、AbstractSetクラスを継承し、同等のスレッドセーフなHashSetのをCopyOnWriteArraySet。CopyOnWriteArrayListと、操作のCopyOnWriteArraySet CopyOnWriteArrayListと原理によって達成された標的を含む内部CopyOnWriteArraySet CopyOnWriteArrayListとは、乱れ同様のではなく、反復設定され、リストは、反復順序付けされます。

CopyOnWriteArrayListと。

リスト1. CopyOnWriteArrayListとはインターフェイスを実装しているので、それはキューです。

2. CopyOnWriteArrayListとは、ロックのメンバーが含まれています。それぞれはCopyOnWriteArrayListとの相互に排他的なアクセスを実現するために、ロックによって、ミューテックスロックと結合していCopyOnWriteArrayListと。
3. CopyOnWriteArrayListとはCopyOnWriteArrayListと性質に実装配列によって示される配列アレイのメンバーを含みます。
「動的配列」と「スレッドセーフ」の原則CopyOnWriteArrayListと2の下からさらに説明。
1. CopyOnWriteArrayListと機構「ダイナミックアレイ」 -データを保持するために、その中に「揮発性アレイ」(アレイ)を有しています。データ「を削除/変更/追加」する場合、新しい配列を作成し、新しい配列に更新されたデータをコピーし、そして最終的には配列がに割り当てられます「揮発性の配列。」それはそれはCopyOnWriteArrayListと呼ばれています理由です!CopyOnWriteArrayListと動的配列は、このようにして達成される。しかし、正確にそれがであるので、データを「削除/変更/追加」新しい配列を作成しますので、操作が変更データ、非効率的なCopyOnWriteArrayListとを含み、単語を見つけることばかりトラバースが、 、より効率的。

2.「スレッドセーフ」メカニズムをCopyOnWriteArrayListと-で実現するために、揮発性およびミューテックスで(01)CopyOnWriteArrayListとはしてデータを保存する「揮発性アレイ。」スレッドは揮発性の配列を読み取ると、あなたは常に、この揮発性変数の他のスレッドによって書き込まれた最後のを見ることができ、したがって、揮発性により、このメカニズムを確保する「常に、最新のデータを読み込む」を提供します。
(02)CopyOnWriteArrayListとは、ミューテックスによってデータを保護します。「削除/変更/追加」のデータでは、最初の「mutexロックを取得する」になり、修正が完了した後、その後、データは最初に「揮発性のアレイ」に更新され、その後、「ミューテックスを解放」;ので、到達データ保護の目的。 

のはCopyOnWriteArrayListと元のコアを見てみましょう:

 

パブリック ブールの追加(E E){
 最終的なロック= ReentrantLockののこのの.lockは;
 // 「ロック」GET 
Lock.lock()を、
 試してみる{
 // 元のデータのデータ長との「揮発性配列を」ゲット。
オブジェクト[] =要素のgetArray();
 int型のlen = elements.length;
 //は新しい配列たnewElementsを作成し、そしてたnewElementsに元のデータをコピー;
 // + +1 =「元の配列の長さ」配列たnewElementsの長さ 
オブジェクト[] = Arrays.copyOfたnewElements(要素、LEN + 1 );
 // たnewElementsに格納されている"新たに追加された要素"。
たnewElements [LEN] = E;
 // に割り当てられたnewElements "揮発性アレイ。" 
setArray(たnewElements);
 リターン 真へ; 
} 最後に{
 // "ロック"放出
)(lock.unlockを; 
} 
}

説明: スレッドのロックを取得するには、この時点での必要性がある場合はまず、排他ロック(ロック)、「アクションの追加」開始する前に、あなたは待つ必要があり、操作が完了した後、(ロック)排他ロックを解除し、この時点で他のスレッドロックを取得するためです。複数のスレッドを防ぐために排他的ロックを通じて同時にデータを変更します!以下のように定義されているロック:ReentrantLockののReentrantLockの過渡最終ロック新しい新=();

        第二に、完成したときに、はsetArrayによる「揮発性配列」()に更新されます。また、私たちは「いつも、揮発性の変数を読み取るために、(任意のスレッド)最後の書き込みこの揮発性の変数を参照してくださいすることが可能であること」、先に述べた;ので、各添加元素の後に、他のスレッドは、新しく追加された見ることができます要素。

 ReentrantLockのミューテックスが同期に比べ、java.util.concurrentパッケージで入手可能であるので、ReentrantLockのクラスには、いくつかの高度な機能を提供します。

待機中のスレッドの長い期間のためのロック解除を保持しているスレッドがデッドロックを回避することができます同期の状況に相当する、待ってあきらめることを選択することができたときに1、割り込みを待ちます。

2.フェアロックは、同じロックを待っている複数のスレッドは、ロックが年代順のアプリケーションのロックで取得する必要があります

3.ロック状態の複数の結合、複数のオブジェクトを同時にReentrantLockのオブジェクトを結合させることができます。ReentrantLockのスレッドを実装条件(条件)クラスが好き同期またはランダムウェイクアップ、ウェイクアップウェイクアップする必要がないグループ化されて提供してスレッドまたはすべてのスレッドを覚まします。

簡単に言えば、ReentrantLockのは、スピンロックで実現するために、ロックは、CASの動作サイクルを呼び出すことによって達成されます。その性能は、カーネルモードにスレッドがブロックされているため避けるためにも良いです。

 

地図

ConcurrentHashMapのとConcurrentSkipListMapのを:JUCであっ地図実装クラスのパッケージを設定します。そのフレーム以下に示すように:
ここに画像を挿入説明

それはクラスから継承クラスAbstractMap、及びのConcurrentMapインタフェースを実装する;(01)ConcurrentHashMapのスレッドセーフなハッシュ・テーブル(等価なスレッドセーフのHashMap)です。達成するための「ロック・セグメント」を通じてConcurrentHashMapのは、それが同時サポートしています。
それはクラスから継承クラスAbstractMap、及びConcurrentNavigableMapインタフェースを実装する;(02)ConcurrentSkipListMapのは、スレッドセーフで秩序ハッシュテーブル(等価なスレッドセーフのTreeMap)です。ConcurrentSkipListMapの同時サポートし、「ジャンプリスト」、によって達成されます。
(03)ConcurrentSkipListSetのは、スレッドセーフ(等価スレッドセーフTreeSetの)の順序集合であり、それはAbstractSetから継承、及びのNavigableSetインタフェースを実現します。ConcurrentSkipListSetのがConcurrentSkipListMapのことで達成され、それはまた、同時サポートしています。

ConcurrentHashMapの。

1、ConcurrentHashMap是线程安全的哈希表,它是通过“锁分段”来实现的。ConcurrentHashMap中包括了“Segment(锁分段)数组”,每个Segment就是一个哈希表,而且也是可重入的互斥锁。第一,Segment是哈希表表现在,Segment包含了“HashEntry数组”,而“HashEntry数组”中的每一个HashEntry元素是一个单向链表。即Segment是通过链式哈希表。第二,Segment是可重入的互斥锁表现在,Segment继承于ReentrantLock,而ReentrantLock就是可重入的互斥锁。
对于ConcurrentHashMap的添加,删除操作,在操作开始前,线程都会获取Segment的互斥锁;操作完毕之后,才会释放。而对于读取操作,它是通过volatile去实现的,HashEntry数组是volatile类型的,而volatile能保证“即对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入”,即我们总能读到其它线程写入HashEntry之后的值。 以上这些方式,就是ConcurrentHashMap线程安全的实现原理。

(01) put()根据key获取对应的哈希值,再根据哈希值找到对应的Segment片段。如果Segment片段不存在,则新增一个Segment。
(02) 将key-value键值对添加到Segment片段中。

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// tryLock()获取锁,成功返回true,失败返回false。
// 获取锁失败的话,则通过scanAndLockForPut()获取锁,并返回”要插入的key-value“对应的”HashEntry链表“。
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
// tab代表”当前Segment中的HashEntry数组“
HashEntry<K,V>[] tab = table;
// 根据”hash值“获取”HashEntry数组中对应的HashEntry链表“
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
// 如果”HashEntry链表中的当前HashEntry节点“不为null,
if (e != null) {
K k;
// 当”要插入的key-value键值对“已经存在于”HashEntry链表中“时,先保存原有的值。
// 若”onlyIfAbsent“为true,即”要插入的key不存在时才插入”,则直接退出;
// 否则,用新的value值覆盖原有的原有的值。
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
// 如果node非空,则将first设置为“node的下一个节点”。
// 否则,新建HashEntry链表
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
// 如果添加key-value键值对之后,Segment中的元素超过阈值(并且,HashEntry数组的长度没超过限制),则rehash;
// 否则,直接添加key-value键值对。
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
// 释放锁
unlock();
}
return oldValue;
}

 

说明:
put()的作用是将key-value键值对插入到“当前Segment对应的HashEntry中”,在插入前它会获取Segment对应的互斥锁,插入后会释放锁。具体的插入过程如下:
(01) 首先根据“hash值”获取“当前Segment的HashEntry数组对象”中的“HashEntry节点”,每个HashEntry节点都是一个单向链表。
(02) 接着,遍历HashEntry链表。
       若在遍历HashEntry链表时,找到与“要key-value键值对”对应的节点,即“要插入的key-value键值对”的key已经存在于HashEntry链表中。则根据onlyIfAbsent进行判断,若onlyIfAbsent为true,即“当要插入的key不存在时才插入”,则不进行插入,直接返回;否则,用新的value值覆盖原始的value值,然后再返回。
       若在遍历HashEntry链表时,没有找到与“要key-value键值对”对应的节点。当node!=null时,即在scanAndLockForPut()获取锁时,已经新建了key-value对应的HashEntry节点,则”将HashEntry添加到Segment中“;否则,新建key-value对应的HashEntry节点,然后再“将HashEntry添加到Segment中”。 在”将HashEntry添加到Segment中“前,会判断是否需要rehash。如果在添加key-value键值之后,容量会超过阈值,并且HashEntry数组的长度没有超过限制,则进行rehash;否则,直接通过setEntryAt()将key-value键值对添加到Segment中。

 

2、之前是分段锁的思想,通过采用分段锁Segment减少热点域来提高并发效率。1.8之后利用CAS+Synchronized来保证并发更新的安全,底层采用数组+链表+红黑树的存储结构。

    • CAS(Compare-And-Swap) 算法是硬件对于并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于
      管理对共享数据的并发访问;
    • CAS 是一种无锁的非阻塞算法的实现;
    • CAS 包含了三个操作数:
      • 需要读写的内存值: V
      • 进行比较的预估值: A
      • 拟写入的更新值: B
      • 当且仅当 V == A 时, V = B, 否则,将不做任何操作;

这里来看看ConcurrentHashMap的put源码:

コードをコピー
 
 

public V put(K key, V value) {
return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
//和hashMap不同的是,concurrentHashMap的key和value都不允许为null
//concurrenthashmap它们是用于多线程的,并发的 ,如果map.get(key)得到了null,
// 不能判断到底是映射的value是null,还是因为没有找到对应的key而为空,
// 而用于单线程状态的hashmap却可以用containKey(key) 去判断到底是否包含了这个null。
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;
//如果是第一次put,进行初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//根据(tab.length - 1) & hash 计算目标节点在数组中的位置
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//如果为空,则通过cas 添加一个新建一个头节点
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//hash为-1 说明是一个forwarding nodes节点,表明正在扩容
else if ((fh = f.hash) == MOVED)
//帮助扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//对上面计算出来的节点进行加锁
synchronized (f) {
//这里判断下有没有线程对数组进行了修改
if (tabAt(tab, i) == f) {
//这里如果hash值是大于等于0的说明是链表
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
//如果找到了目标节点,那么进行值替换
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
//如果循环到链表结尾还没发现,那么进行插入操作
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//如果是树
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;
}
}
}
}
//如果链表数量大于TREEIFY_THRESHOLD(8),开始执行转换树
IF(!BinCount = 0){
IF(BinCount> = TREEIFY_THRESHOLD)
treeifyBin(タブ、I)は、
IF(!OLDVAL = NULL)
; OLDVAL戻り
、BREAKを
}
}
}
の要素数の//統計を、拡張かどうかを判断する
(addCount 1L、BinCount);
戻りNULL;
}

コードをコピー

 ConcurrentHashMapのは、ノンブロッキングなるように設計されています。失敗のスレッドがあるか、それが失敗し、他のスレッドに影響を与えたり、アルゴリズムを掛けるべきではないハング。

更新が部分ロックされたときにデータの一部ではなく、テーブル全体がロックされています。同期読み出し動作が完全に非ブロッキングです。利点は、合理的な前提の同期、高効率を確保することです。

 

おすすめ

転載: www.cnblogs.com/kise-ryota/p/11221517.html