JAva高同時ロックの3つの実装

3732 ワード

ロックといえばsynchronizedキーワードを思い浮かべるかもしれませんが、それを使用するとすべての同時問題を解決することができますが、システムのスループットにはより高い要求があります.ここでは、ロックの粒度を減らし、システムの同時能力を高めるためのいくつかの小さなテクニックを提供しています.
初歩的なテクニック-楽観的なロック楽観的なロックは、読むことが衝突せず、書くことが衝突するシーンに適しています.同時に読む頻度は書くよりはるかに大きい.
次のコードを例にとると、悲観的なロックの実装:
public Object get(Object key) {  
  synchronized(map) {  
     if(map.get(key) == null) {  
        // set some values  
     }  
 
      return map.get(key);  
  }  
}  

楽観的ロックの実現:
public  Object get(Object key) {  
  Object val = null;  
  if((val = map.get(key) == null) {  
      //  map null   
      synchronized (map) {  
          if(val = map.get(key) == **null**) {  
              // set some value to map...  
          }  
       }  
  }  
 
   return  map.get(key);  
}  

中級テクニック-String.intern()楽観的なロックは、大量の書き込み競合の問題をうまく解決できませんが、多くのシーンでは、ロックは実際にはユーザーまたは注文にのみ適用されます.たとえば、後続の操作を行うには、ユーザーがセッションを作成する必要があります.しかし、ネットワーク上の理由により、ユーザーセッションの作成要求と後続要求はほぼ同時に達成され、パラレルスレッドは後続要求を先に処理する可能性があります.一般的には、上記の楽観的なロックなど、ユーザーsessionMapにロックをかける必要があります.このようなシナリオでは,ロックはユーザ自身に限定され,すなわち従来の
lock.lock();
   int num=storage.get(key);
   storage.set(key,num+1);
lock.unlock();
 :
lock.lock(key);
   int num=storage.get(key);
   storage.set(key,num+1);
lock.unlock(key);

この比較はデータベース・テーブル・ロックとロー・ロックの概念に類似しており、ロー・ロックの同時性はテーブル・ロックよりもはるかに高いことが明らかになった.
Stringを使用します.inter()はこのような考え方の具体的な実現である.クラスStringは文字列プールを維持します.Internメソッドが呼び出されると、プールにこのStringオブジェクトに等しい文字列がすでに含まれている場合(オブジェクトはequals(Object)メソッドによって決定される)、プール内の文字列が返されます.Stringが同じである場合、String.intern()は常に同じオブジェクトを返すため,同じユーザへのロックを実現する.ロックの粒度は特定のユーザに限られるため,システムは最大限の同時性を得た.
public      void doSomeThing(String uid) {  
   synchronized  (uid.intern()) {  
       // ...  
   }  
}  

CopyOnWriteMap? 「データベース内のロー・ロックに似た概念」といえばMVCC、JavaではCopyOnWriteクラスがMVCCを実現している.Copy On Writeはこのようなメカニズムです.共有データを読み込むときは、同期する必要はありません.私たちがデータを修正するときは、現在のデータをコピーして、このコピーに修正して、完成したら、修正したコピーで、元のデータを置き換えます.この方法をCopy On Writeと言います.しかし、JDKはCopyOnWriteMapを提供していません.なぜですか.次の良い答えは、すでにConcurrentHashMapがあるのに、なぜCopyOnWriteMapが必要なのかということです.
Fredrik BromeeはI guess this depends on your use case,but why would you need a CopyOnWriteMap when you already have a ConcurrentHashMap?For a plain lookup table with many readers and only one or few updates it is a good fit.Compared to a copy on write collection:Read concurrency:Equal to a copy on write collection. Several readers can retrieve elements from the map concurrently in a lock-free fashion.Write concurrency:Better concurrency than the copy on write collections that basically serialize updates (one update at a time). Using a concurrent hash map you have a good chance of doing several updates concurrently. If your hash keys are evenly distributed.If you do want to have the effect of a copy on write map, you can always initialize a ConcurrentHashMap with a concurrency level of 1.
高度なテクニック-クラスConcurrentHashMap
String.inter()の欠点はクラスStringが文字列プールを維持するのはJVM perm領域に置かれていることであり、ユーザー数が特に多い場合、文字列プールに置かれたStringが制御不能になり、OOMエラーやFull GCが多すぎる可能性がある.ロックの個数を制御しながら、粒度ロックを減らすにはどうすればいいですか?Java ConcurrentHashMapを直接使用しますか?あるいはあなたは自分のもっと細かいコントロールに参加したいですか?では、ConcurrentHashMapの方法を参考にして、ロックが必要なオブジェクトを複数のbucketに分割し、各bucketにロックを追加することができます.擬似コードは以下の通りです.
Map locks = new Map();  
List lockKeys =  new List();  
 for ( int  number : 1 - 10000) {  
   Object lockKey =  new  Object();  
   lockKeys.add(lockKey);  
    locks.put(lockKey, new Object());  
}  
public  void doSomeThing(String uid) {  
   Object lockKey = lockKeys.get(uid.hash() % lockKeys.size());  
   Object lock = locks.get(lockKey);  
     
   synchronized (lock) {  
      // do something  
   }  
}