同時プログラミングのJavaロック
7821 ワード
一、再入錠
ロックはデータを同時共有し、一貫性を保証するツールとして、JAVAプラットフォーム(synchronized(重量級)やReentrantLock(軽量級)など)で多くの実装が行われています.これらのすでに書かれたロックは、私たちの開発に便利を提供しています.
再入ロックは、再帰ロックとも呼ばれ、同じスレッドの外層関数がロックを取得した後も、内層再帰関数はロックを取得するコードを持っているが、影響を受けないことを意味する.
JAVA環境ではReentrantLockもsynchronizedも再ロック可能
synchronized:
public class Test implements Runnable {
public synchronized void get() {
System.out.println("name:" + Thread.currentThread().getName() + " get();");
}
public synchronized void set() {
System.out.println("name:" + Thread.currentThread().getName() + " set();");
get();
}
@Override
public void run() {
set();
}
public static void main(String[] args) {
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
ReentrantLock:
public class Test02 extends Thread {
ReentrantLock lock = new ReentrantLock();
public void get() {
try{
lock.lock();
System.out.println(Thread.currentThread().getId());
}catch (Exception e){
}finally {
lock.unlock();
}
}
public void set() {
try{
lock.lock();
System.out.println(Thread.currentThread().getId());
get();
}catch (Exception e){
}finally {
lock.unlock();
}
}
@Override
public void run() {
set();
}
public static void main(String[] args) {
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
通常、関数が自分で完了するとロックが解放されます.以上の2つの方法は、ロックが取得された関数でロックが必要な関数を再呼び出し、再入力性がなければ、新しいロックが必要な関数を呼び出すと、デッドロックになります.再利用可能なロックは、再帰呼び出しにおいて非常に重要です.
二、読み書きロック
プログラムには、共有リソースの読み取りおよび書き込み操作が含まれており、書き込み操作は読み取り操作ほど頻繁ではありません.書き込み操作がない場合、2つのスレッドが1つのリソースを同時に読むのは問題ないので、複数のスレッドが共有リソースを同時に読み取ることができるようにする必要があります.
しかし、これらの共有リソースを書きたいスレッドがある場合は、他のスレッドがそのリソースを読み書きするべきではありません(つまり、読み取り-読み取りが共存し、読み取り-書き込みが共存できず、書き込み-書き込みが共存できません).
この問題を解決するには、読み取り/書き込みロックが必要です.Java 5はjavaにあります.util.concurrentパッケージにはすでに読み書きロックが含まれています.
public class Cache {
static Map map = new HashMap();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
// key value
public static final Object get(String key) {
try {
r.lock();
System.out.println(" ,key:" + key + " ");
Thread.sleep(100);
Object object = map.get(key);
System.out.println(" ,key:" + key + " ");
System.out.println();
return object;
} catch (InterruptedException e) {
} finally {
r.unlock();
}
return key;
}
// key value, value
public static final Object put(String key, Object value) {
try {
w.lock();
System.out.println(" ,key:" + key + ",value:" + value + " .");
Thread.sleep(100);
Object object = map.put(key, value);
System.out.println(" ,key:" + key + ",value:" + value + " .");
System.out.println();
return object;
} catch (InterruptedException e) {
} finally {
w.unlock();
}
return value;
}
//
public static final void clear() {
try {
w.lock();
map.clear();
} finally {
w.unlock();
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Cache.put(i + "", i + "");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Cache.get(i + "");
}
}
}).start();
}
}
三、悲観錠、楽観錠
1、楽観錠
常にコンカレントの問題は発生しないと考えられており、データを取り出すたびに他のスレッドがデータを修正しないと考えられているため、鍵はかけられないが、更新時に他のスレッドがそれ以前にデータを修正したかどうかを判断し、バージョン番号メカニズムやCAS操作で実現するのが一般的である.
バージョン方式:一般的にデータテーブルにデータバージョン番号バージョンフィールドを追加し、データが変更された回数を表し、データが変更された場合、バージョン値が1つ追加されます.スレッドAがデータ値を更新しようとすると、データの読み込みと同時にバージョン値が読み出され、更新がコミットされると、先ほど読み込んだバージョン値が現在のデータベースのバージョン値と等しい場合に更新されます.そうしないと、更新が成功するまで更新操作を再試行します.
コアSQL文:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
CAS操作方式:compare and swapまたはcompare and setであり、3つの操作数、データが存在するメモリ値、予想値、新しい値に関連する.更新が必要な場合は、現在のメモリ値が以前に取った値と等しいかどうかを判断し、等しい場合は新しい値で更新し、失敗した場合は再試行し、一般的にはスピン操作、すなわち継続的な再試行である.
2、悲観ロック
常に最悪の場合を想定し、データを取得するたびに他のスレッドが変更されると考えられるため、ロック(リードロック、ライトロック、ローロックなど)が加算され、他のスレッドがデータにアクセスしようとすると、ブロックして停止する必要があります.ロー・ロック、リード・ロック、ライト・ロックなど、データベースに依存して実現できるのは、操作前にロックされ、Javaではsynchronizedの考え方も悲観的なロックです.
四、原子類
java.util.concurrent.atomicパッケージ:原子クラスの小さなツールパッケージで、単一の変数でロックを解除するスレッドの安全なプログラミングをサポートします
原子変数クラスは汎化volatile変数に相当し,原子のおよび条件付きの読み取り−変更−書き込み動作をサポートすることができる.
AtomicIntegerはintタイプの値を表し、getとsetメソッドを提供します.これらのVolatileタイプのint変数は読み取りと書き込みで同じメモリの意味を持っています.また、原子のcompareAndSetメソッド(このメソッドが正常に実行されると、volatile変数の読み取り/書き込みと同じメモリ効果が実現される)、原子の追加、増分、減算などの方法も提供されます.AtomicIntegerは表面的には拡張されたCounterクラスによく似ていますが、競合が発生した場合、ハードウェアの同時サポートを直接利用するため、より高い伸縮性を提供することができます.
なぜ原子類CAS:Compare and Swapがあるのか、すなわち比較して交換する.
jdk 5はjavaを追加して発注する.util.concurrent.*,その次のクラスはCASアルゴリズムを用いてsynchronouse同期ロックとは異なる楽観的なロックを実現した.mysqlのversionフィールドに似た楽観的なロック
同じ変数が複数のスレッドにアクセスされる場合は、パッケージ内のクラスを使用できます.
五、CAS無ロックモード
CAS:Compare and Swap、すなわち比較再交換.jdk 5はjavaを追加して発注する.util.concurrent.*,その次のクラスはCASアルゴリズムを用いてsynchronouse同期ロックとは異なる楽観的なロックを実現した.JDK 5以前のJava言語はsynchronizedキーワードで同期を保証していたが、これは独占ロックであり、悲観ロックでもある.
1、CASアルゴリズムの理解
2、CASの欠点
CASにはABA問題という明らかな問題がある.
質問:変数Vが初めて読み込まれたときがAで、付与の準備中にAであることが確認された場合、他のスレッドによって値が変更されていないことを説明できますか?
この間にBに変更され、Aに変更された場合、CAS操作は変更されたことがないと勘違いします.この場合、javaおよび発注には、変数値のバージョンを制御することによってCASの正確性を保証するタグ付き原子参照クラスAtomicStampedReferenceが提供される.
個人ブログカタツムリ