HashMapのマルチスレッドの問題分析 - 正常と異常rehash1(アリ)HashMapのマルチスレッドの解析

 

マルチスレッドプットは、無限ループを取得するためにつながる可能性

私たちのJavaコードを過ぎて何らかの理由でこの事をHashMapを使用していますが、時にプログラムがシングルスレッドで、すべては問題ありません。その後、私たちのアプリケーションのパフォーマンスの問題は、あなたが行の後、多くの場合、CPUの100%を占めたプログラムを見つけなるために、マルチスレッドなので、複数のスレッドになるために必要な、スタックを参照するには、HashMapの中のプログラムにハングを見つけるだろう問題がなくなった後、この上に.get()メソッドは、プログラムを再起動します。しかし、時間をかけてきます。また、テスト環境で問題が再現することが困難な場合があります。

私たちは、単に私たちはHashMapのが動作する複数のスレッドを知っている、私たち自身のコードを見てください。ドキュメントでは、JavaのHashMapが、ConcurrentHashMapの必要があるスレッドセーフではないことを言いました。しかし、ここで私たちは、なぜ勉強することができます。次のように単純なコードは次のとおりです。

パッケージcom.king.hashmap。

輸入java.util.HashMapを; 

パブリッククラスTESTLOCK { 

    プライベートHashMapのマップ=新しいHashMapの(); 

    公共TESTLOCK(){  スレッドT1 =新しいスレッド(){  ます。public void 実行(){(int型私= 0;私は<50000; I ++用){map.put(新しい整数(I)、I); }のSystem.out.println( "T1オーバー" )。}}。スレッドT2 =新しいスレッド(){ます。public void 実行(){のために(int型私= 0;私は<50000; I ++ ){map.put(新しい整数(I)、I); }のSystem.out.println( "T2オーバー" )。}}。スレッドT3 =新しいスレッド(){ます。public void 実行(){のために(私は++;;私<50000 iは= 0 int型(新規{map.putを)整数(I)、I); }のSystem.out.println( "T3オーバー" )。}}。スレッドT4 =新しいスレッド(){ます。public void 実行(){のために(int型私= 0;私は<50000; I ++ ){map.put(新しい整数(I)、I); }のSystem.out.println( "T4オーバー" )。}}。スレッドT5 =新しいスレッド(){ます。public void 実行(){のために(int型私= 0;私は<50000; I ++ ){map.put(新しい整数(I)、I); }のSystem.out.println( "T5オーバー" )。}}。スレッドT6 =新しいスレッド(){ます。public void 実行(){のために(int型私= 0;私は<50000; I ++ ){map.get(新しい整数(I)); }のSystem.out.println( "T6オーバー" )。}}。スレッドT7は=新しいスレッド(){公共ボイド(;;私は50000を<I ++がI = 0をint型の実行(){ {マップ)。整数(I)); }のSystem.out.println( "T7オーバー" )。}}。スレッドT8 =新しいスレッド(){ます。public void 実行(){のために(int型私= 0;私は<50000; I ++ ){map.get(新しい整数(I)); }のSystem.out.println( "T8オーバー" )。}}。スレッドT9 =新しいスレッド(){ます。public void 実行(){のために(int型私= 0;私は<50000; I ++ ){map.get(新しい整数(I)); }のSystem.out.println( "T9オーバー" )。}}。スレッドT10 =新しいスレッド(){ます。public void 実行(){のために(int型私= 0;私は<50000; I ++ ){map.get(新しい整数(I)); }のSystem.out.println( "オーバーT10" )。}}。t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); t6.start(); t7.start(); t8.start(); t9.start(); t10.start();メイン(文字列[]引数){新しいTESTLOCK()。}}

 

それは、HashMapの中に入れて一定の非スレッドセーフ内容/コンテンツを取得し、10個のスレッドを開始することで、コンテンツは非常に簡単に言えば、主要な成長と価値はよくやっていないから0(このプットから整数です、)後、私は思った問題の分析を妨害するようにします。同時書き込みのために行うハッシュマップ、私は状況がダーティデータを持っていますが、繰り返しこのプログラムを実行すると思った、スレッドT1があるでしょう、T2は、ほとんどの場合、スレッドは別の成功裡の妥結ライブハングで、ライブの状況をハングアップです時折、10件のスレッドが生きているハング。

アクション「HashMapの」データ構造 - 根本的な原因は、共有変数保護されていないの無限ループにあります。結局操作方法に「同期」に追加すると、すべてが正常に戻りました。JVMこれはバグでしょうか?ありません、この現象は報告書が出てきた長い前にいることを指摘しておかなければ。Sunのエンジニアが、これはバグだと思うが、そのようなシナリオでは、「ConcurrentHashMapの」の使用を推奨しません。

CPU時間を占有し、いくつかのスレッドが実行中で、その結果、無限ループの出現があったので、CPUの使用率が通常です。その理由は、複数のスレッドが、問題はそのようにして生成されたキー値が無限ループエントリキーの一覧を引き起こし入れたときに、問題のHashMapのは、スレッドセーフではありませんということです。

これは常に実行されます時に別のスレッドがキー無限ループのエントリーリストを取得する場合。最終的な結果は出てサーバー一顧につながる、スレッドの無限ループの増加です。私たちは、一般的にHashMapの繰り返し挿入時間の前回値の値は、この権利によってカバーされると信じています。しかし、マルチスレッド環境ではなく、同期の場合のために、その内部実装のメカニズムのマルチスレッドアクセス、(のために、焼き直しのアクションを行うと同時に、複数のスレッドをもたらすことができると同じHashMapの上での動作を置けばいい、それがループキーにつながる可能性テーブルには、スレッドが表示されたら、セキュリティ上の問題が発生した可能性がある上、)高いCPU使用率で、その結果、CPUを占有し続け、終了しません、表示されます。

スタックのツールは、そのサーバーの問題をjstack使用して情報をダンプします。無限ループは、それが最初に次のようにあなたのコードで問題を見つけ、実行可能スレッドを探します。

java.lang.Thread.State:RUNNABLE 
java.util.HashMap.get(HashMap.java:303で
com.sohu.twap.service.logic.TransformTweeter.doTransformTweetT5(TransformTweeter.java:183で
共出现了23次。
java.lang.Thread.State:RUNNABLE 
java.util.HashMap.put(HashMap.java:374で com.sohu.twap.service.logic.TransformTweeter.transformT5(TransformTweeter.java:816で 共出现3了次。

 

注意:非合理的な使用にHashMapのリードではなく、デッドロックの無限ループです。

要素が失われる可能性があり、時間をかけマルチスレッディング

の成功要素が存在する場合の主な問題は、2つのスレッドが同時に電子を行った場合、それらは、次の要素eであり、テーブルに割り当てられ、addEntry新しいエントリ(ハッシュ、キー、値、E)の方法であります失われました。

後に置く非null要素はそれから抜け出すnullであります

:転写法、次のコードで

ボイド転送(エントリー[] NEWTABLE){ 
    エントリー[] SRC = テーブルと 
    int型newCapacity = newTable.length。
    (INT J = 0; J <src.length; J ++用){  エントリE = SRC [J]。 (もし!E = NULL ){SRC [J] = nullを実行{エントリの次= e.next。int型I = indexFor(e.hash、newCapacity)。e.next = NEWTABLE [I]。NEWTABLE [I] = E。E = 次回。}ながら(!E = NULL ); }}}

 

この方法では、SRCに古い配列の割り当てでは、null以外のsrc要素SRCを、横断、その要素は、今後の古い配列要素はnullに設定されている中で、ヌルsrcに設定されていることでしょう、それはこの文です。

if (e != null) { src[j] = null;

HashMapのデータ構造

私は簡単にHashMapのこの古典的なデータ構造を言う必要があります。

HashMapのは、通常、[キーが追加されたすべてのキー、iは、ハッシュアルゴリズムを介してキー配列によって計算されたインデックスを分散し、テーブルにこれを置くか(表[]とする)ポインタ配列を使用しますi]は、また衝突として知られているので、競合と呼ばれる同じキーI上の2つの異なる事業者が、存在する場合、それは]テーブルの上にリンクリスト[Iを形成することになります。

私たちは、[]のサイズのテーブルが小さい、2つだけを言って、あなたは10個のキーを入れたい場合は、非常に頻繁にそれから衝突するので、O(1)検索アルゴリズムであれば、それはリンクリストトラバーサルとなっていることを知って、性能変化欠陥ハッシュテーブルであり、O(n)となります。

だから、ハッシュテーブルのサイズと容量が非常に重要です。一般に、データが挿入される場合、このコンテナハッシュテーブルは、ハッシュテーブルのサイズを大きくする必要があるよりも多くの場合、容量は、セットを超えるがthredholdチェックしますが、この方法は、全体のハッシュテーブルの要素が必要とされますこれは、再カウントして再び。これは焼き直しと呼ばれ、このコストは非常に大きいです。

ソースコードのHashMapの焼き直し

以下では、Javaソースコード内のHashMapを見てください。キーを入れて、ハッシュテーブルの値:

公共V PUT(キーK、V値)
{ 
    ... 
    //計算されたハッシュ値
    INT =ハッシュハッシュ(key.hashCode()); 
    INT I = indexFor(ハッシュ、table.length); 
    //この場合キーは古い値(リンク操作)を交換、挿入されている
    ため(エントリ<K、V>表E = [I]は、E =ヌル;! E = e.next){  オブジェクトK;  IF(e.hashハッシュ&& ==((K = e.key)== ||キーkey.equals(K))){  V = OLDVALUE e.Value; e.Value = 値; e.recordAccess(この);戻りOLDVALUEを;} ModCount ++} ; //キーが存在しない場合、ノードは増やす必要addEntry(ハッシュ、キー、値、I)を、戻りNULL ;}

 

超えている容量を確認してください:

addEntryを(ハッシュINT、Kキー、Vの値は、int型無効bucketIndex)を
{ 
    エントリ<K、V> E = 表[bucketIndex]; 
    表[bucketIndex] =新しい新規エントリ<K、V> ハッシュ、キー、値、E) ; 
    //チェック現在のサイズは、我々は、しきい値を設定したしきい値を超えた場合、超えた場合は、サイズ変更の必要性
    IF(サイズ++> = しきい値) リサイズの(2 * table.length);  }

 

より大きなハッシュテーブルを作成し、新しいハッシュテーブルのハッシュテーブルに古いからデータを移行。

リサイズボイド(INT newCapacity)
{ 
    エントリー[] = oldtable 表; 
    INT = oldCapacity oldTable.length; 
    ... 
    //新しいテーブルを作成するハッシュ
    エントリ[] =新しい新しいNEWTABLE エントリー[newCapacity];  //オールド新たなハッシュテーブルへの移行表ハッシュは、データ 、転送(NEWTABLE) 表= NEWTABLE;閾値=(INT)(* newCapacity loadFactor);}

 

ソースコードの移行、でノートを強調:

ボイド転送(エントリは、[] NEWTABLE)
{ 
    エントリ[] SRC = 表; 
    INT = newCapacity newTable.length; 
    //次のコードは、意味:
    //が出てくるから要素を選択し、次にNEWTABLEに入れOldTable 
    (ためJ 0 = INT; J <src.length; J ++ ){  エントリ<K、V> E = SRC [J];  !IF(E = NULL ){SRC [J] = NULL ; DO {エントリ<K、V> =次e.next; I = INT indexFor(e.hash、newCapacity); e.next = NEWTABLE [I]; NEWTABLE [I] = E; E = 次に;}ながら(E = NULL!);}}}

 

さて、このコードは比較的正常です。そして、問題はありません。

焼き直しの通常の過程

彼がプレゼンテーションを行いました図を描きました。

  1. 私は、私たちのハッシュアルゴリズムは、テーブルのサイズに関する重要なMOD(つまり、配列の長さである)を持つ単純であると仮定します。

トップは、古いハッシュテーブルのサイズであるハッシュテーブル= 2、ここでは全ての紛争TABLE1後のMODキー= 3、7、5、2。

次の3つのステップは、すべての焼き直しプロセスを再ハッシュテーブル4のサイズを変更します。

 

並行プロセスの焼き直し

(1)私たちは二つのスレッドがあるとします。私は赤と水色でそれをマーク。私たちは、転送コードこの詳細を振り返ります:

実行{ 
    エントリ<K、V>次に= e.next; // < -ハングするようにスケジュールされたスレッドを実行するものと
    INT I = indexFor(e.hash、newCapacity); 
    e.next = NEWTABLEを[I ]; 
    NEWTABLE [I] = E;  E = 次に;  }ながら(!E = NULL);

 

そして、我々は二つのスレッドの実装を完了しました。だから我々はこのように、以下の持っています。

注:Eスレッド1は、2つの組換え後にスレッドの鍵(7)と、2つのスレッドの焼き直しで、リストのポイントにキー(3)、次の点を指すため。順序が逆になった後、我々はリストを見ることができます。

(2)スレッドがバック実行が予定されています。

  1. まず、実行newTalbe [i]を= E。

次いで、E =次、電子ポインティング・キー(7)が得られます。

そして次= e.nextの次のサイクルは、次のポインティングキー(3)を導きました。

(3)すべてが順調です。

その後、仕事を通します。最初NEWTABLEにキー(7)オフ、[I]、E及び次ダウンシフト。

(4)円形のリンクが表示されます。

e.next = NEWTABLE [I]キーを生じる(3)キーを指し.next(7)。注:この時点で、キーは(7)まだそこに、キー(3)、循環リンクリストを指して.next。

我々は、スレッド、HashTable.get(11)に呼び出すときに、悲劇が--Infiniteループが登場しました。

 

三つのソリューション

Hashtable替换HashMap

Hashtable 是同步的,但由迭代器返回的 Iterator 和由所有 Hashtable 的“collection 视图方法”返回的 Collection 的 listIterator 方法都是快速失败的:在创建 Iterator 之后,如果从结构上对 Hashtable 进行修改,除非通过 Iterator 自身的移除或添加方法,否则在任何时间以任何方式对其进行修改,Iterator 都将抛出 ConcurrentModificationException。因此,面对并发的修改,Iterator 很快就会完全失败,而不冒在将来某个不确定的时间发生任意不确定行为的风险。由 Hashtable 的键和值方法返回的 Enumeration 不是快速失败的。

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误做法:迭代器的快速失败行为应该仅用于检测程序错误。

Collections.synchronizedMap将HashMap包装起来

返回由指定映射支持的同步(线程安全的)映射。为了保证按顺序访问,必须通过返回的映射完成对底层映射的所有访问。在返回的映射或其任意 collection 视图上进行迭代时,强制用户手工在返回的映射上进行同步:

Map m = Collections.synchronizedMap(new HashMap());
...
Set s = m.keySet();  // Needn't be in synchronized block
...
synchronized(m) {  // Synchronizing on m, not s!
Iterator i = s.iterator(); // Must be in synchronized block
    while (i.hasNext())
        foo(i.next());
}

 

不遵从此建议将导致无法确定的行为。如果指定映射是可序列化的,则返回的映射也将是可序列化的。

ConcurrentHashMap替换HashMap

予想回収のと更新のハッシュテーブル調節可能な同時実行をサポートフル同時実行。そのような機能は、同一仕様のハッシュテーブルを遵守し、ハッシュテーブルの方法の各バージョンに対応する方法を含みます。しかし、すべての操作はスレッドセーフですが、検索操作をロックする必要はありませんし、テーブル全体をロックするすべてのアクセスを防ぐためにいくつかの方法でサポートされていない場合でも。このようなプログラムは、スレッドセーフ、および余分な細部との同期に応じて、ハッシュテーブルと完全に相互運用することができます。
(GETを含む)検索操作は、一般的にブロックするので、(入れ、削除など)の更新操作と重複してもよいしません。最近完了した更新操作の結果に影響を与える取得。このようなのputAllと明らかなように、いくつかの集合操作については、同時検索は、エントリの挿入および除去の一部だけに影響を及ぼし得ます。あなたは反復子/列挙した後、またはので、作成したとき同様、イテレータと列挙型は、要素がある時点で、ハッシュテーブルの状態に影響を与える返します。彼らはConcurrentModificationExceptionをスローしません。しかし、各反復は、スレッドによってのみ使用されるように設計されています。

参考:HashMapのマルチスレッドの解析

おすすめ

転載: www.cnblogs.com/aspirant/p/11504389.html