ロック機構ReetrankLockとsynchronizedの比較
7984 ワード
下層原理の実現の違い
反発同期の最も主要な問題は、スレッドのブロックと起動によるパフォーマンスの問題であるため、この同期はブロック同期とも呼ばれ、悲観的な同時ポリシー、すなわちスレッドが独占ロックを得ることに属する.排他ロックは、他のスレッドがブロックに依存してスレッドのロック解除を待つしかないことを意味します.一方,CPU変換スレッドがブロックされるとスレッドコンテキスト切替が起こり,多くのスレッド競合ロックがある場合,CPUの頻繁なコンテキスト切替が起こり効率が低下する.synchronizedはこのような同時戦略を採用している.
命令セットの発展に伴い、衝突検出に基づく楽観的な同時戦略は、一般的には先進的な操作であり、他のスレッドが共有データを競合しなければ、操作は成功し、共有データが競合し、競合が発生すれば、では、他の補償措置(最も一般的な補償措置は試行が成功するまで絶えず再ピックアップすることである)を行い、このような楽観的な同時戦略の多くの実現はスレッドを掛ける必要がないため、このような同期は非ブロック同期と呼ばれている.ReetrantLockはこのような同時戦略を採用している.
楽観的な同時戦略では、操作と衝突検出の2つのステップが原子性を備えており、ハードウェア命令によって保証されています.ここではCAS操作を使っています(Compare and Swap).JDK 1.5以降、JavaプログラムはCAS操作を使用することができる.ReentrantLockのソースコードをさらに検討すると、鍵を取得する上で重要な方法の一つはcompareAndSetStateであり、ここでは呼び出したCPUが提供する特殊な命令である.現代のCPUは命令を提供し、共有データを自動的に更新することができ、また他のスレッドの干渉が検出され,compareAndSet()はロックの代わりにこれらを用いた.このアルゴリズムは非ブロックアルゴリズムと呼ばれ、1つのスレッドの失敗または停止が他のスレッドの失敗または停止に影響を与えるべきではないことを意味する.
Java 5には、AutomicInteger、AutomicLong、AutomicReferenceなどの特殊な原子変数クラスが導入されており、compareAndSet()、incrementAndSet()、getAndIncrement()などの方法でCAS操作が使用されている.従って,それらはいずれもハードウェア命令によって保証される原子法である.
用途の比較
基本的な文法上、ReentrantLockはsynchronizedとよく似ていて、それらはすべて同じスレッドの再入特性を備えていますが、コードの書き方に少し違いがあります.1つはAPIレベルの反発ロック(Lock)として表現され、1つは原生文法レベルの反発ロック(synchronized)として表現される.ReentrantLockはsynchronizedに対していくつかの高度な機能を追加し、主に以下の3つがある.
≪待機中断可能|Wait Break|emdw≫:ロックを保持しているスレッドが長期にわたってロックを解放しない場合、待機中のスレッドは待機を放棄し、他の処理に変更することができ、実行時間の非常に高い同期ブロックの処理に役立ちます.synchronizedによって生成される反発ロックを待つと、ブロックされ続け、中断されません.
フェアロックを実現可能:複数のスレッドが同じロックを待機している場合、フェアロックではなく申請ロックの時間順に並んで待機する必要があります.これは保証されません.ロックが解放されると、ロックを待機しているスレッドのいずれかがロックを取得する機会があります.synchronizedのロック時は非フェアロックであり、ReentrantLockはデフォルトでも非フェアロックであるが、構造方法ReentrantLock(ture)によってフェアロックの使用を要求することができる.ロックは複数の条件をバインドできます:ReentrantLockオブジェクトは複数のConditionオブジェクト(条件変数または条件キュー)を同時にバインドできますが、synchronizedでは、ロックオブジェクトのwait()とnotify()またはnotifyAll()メソッドは1つの隠し条件を実現できますが、1つ以上の条件に関連付ける場合は、1つのロックを追加しなければなりません.ReentrantLockはそうする必要はなく、newCondition()メソッドを複数回呼び出すだけです.さらに、現在のスレッドが通知するスレッド(すなわち、Conditionオブジェクトにバインドされた他のスレッド)を、Conditionオブジェクトをバインドすることによって判断することもできる.割り込み可能ロック
ReetrantLockには、割り込みロックと応答割り込みロックを無視する2つのロックがあります.割り込みロックを無視することはsynchronizedによって実現される反発ロックと同様に、割り込みに応答することはできず、応答割り込みロックは割り込みに応答することができる.
あるスレッドAがロック中のコードを実行している場合、別のスレッドBがロックの取得を待っている場合、待ち時間が長すぎるため、スレッドBが待ちたくない可能性があります.他のことを先に処理したい場合は、自分を中断させるか、他のスレッドで中断させることができます.ReetrantLockが割り込みロックを無視することを提供している場合は、割り込みを無視することはできません.スレッドBを待ち続けるのではなく、ReetrantLockが応答割り込みロックを提供している場合、割り込みを処理し、スレッドBに待機を放棄させ、他のことを処理します.
応答割り込みロックを取得する一般的な形式は、次のとおりです.
ここには、分析中断のサンプルコード(ネット上から抜粋)があります.
synchronizedで反発ロックの待機を中断すると、次の例のようにスレッドは機能しません.
実行結果は次のとおりです.
我々は長い間待っていたが,その後も出力がなく,リードスレッドの反発ロックに対する待機が中断されていないこと,すなわち,ユーザがロックを食べてリードスレッドの中断に応答していないことを示した.
上記のコードのsynchronizedの反発ロックをReentrantLockの応答割り込みロックに変更します.つまり、次のコードに変更します.
実行結果は次のとおりです.
結果から,試行中断後にcatch文ブロックの内容が出力され,後続の「読み取り終了」も出力され,スレッドの反発ロックに対する待機が中断されたこと,すなわち,この反発ロックがリードスレッドの中断に応答したことを示す.
反発同期の最も主要な問題は、スレッドのブロックと起動によるパフォーマンスの問題であるため、この同期はブロック同期とも呼ばれ、悲観的な同時ポリシー、すなわちスレッドが独占ロックを得ることに属する.排他ロックは、他のスレッドがブロックに依存してスレッドのロック解除を待つしかないことを意味します.一方,CPU変換スレッドがブロックされるとスレッドコンテキスト切替が起こり,多くのスレッド競合ロックがある場合,CPUの頻繁なコンテキスト切替が起こり効率が低下する.synchronizedはこのような同時戦略を採用している.
命令セットの発展に伴い、衝突検出に基づく楽観的な同時戦略は、一般的には先進的な操作であり、他のスレッドが共有データを競合しなければ、操作は成功し、共有データが競合し、競合が発生すれば、では、他の補償措置(最も一般的な補償措置は試行が成功するまで絶えず再ピックアップすることである)を行い、このような楽観的な同時戦略の多くの実現はスレッドを掛ける必要がないため、このような同期は非ブロック同期と呼ばれている.ReetrantLockはこのような同時戦略を採用している.
楽観的な同時戦略では、操作と衝突検出の2つのステップが原子性を備えており、ハードウェア命令によって保証されています.ここではCAS操作を使っています(Compare and Swap).JDK 1.5以降、JavaプログラムはCAS操作を使用することができる.ReentrantLockのソースコードをさらに検討すると、鍵を取得する上で重要な方法の一つはcompareAndSetStateであり、ここでは呼び出したCPUが提供する特殊な命令である.現代のCPUは命令を提供し、共有データを自動的に更新することができ、また他のスレッドの干渉が検出され,compareAndSet()はロックの代わりにこれらを用いた.このアルゴリズムは非ブロックアルゴリズムと呼ばれ、1つのスレッドの失敗または停止が他のスレッドの失敗または停止に影響を与えるべきではないことを意味する.
Java 5には、AutomicInteger、AutomicLong、AutomicReferenceなどの特殊な原子変数クラスが導入されており、compareAndSet()、incrementAndSet()、getAndIncrement()などの方法でCAS操作が使用されている.従って,それらはいずれもハードウェア命令によって保証される原子法である.
用途の比較
基本的な文法上、ReentrantLockはsynchronizedとよく似ていて、それらはすべて同じスレッドの再入特性を備えていますが、コードの書き方に少し違いがあります.1つはAPIレベルの反発ロック(Lock)として表現され、1つは原生文法レベルの反発ロック(synchronized)として表現される.ReentrantLockはsynchronizedに対していくつかの高度な機能を追加し、主に以下の3つがある.
≪待機中断可能|Wait Break|emdw≫:ロックを保持しているスレッドが長期にわたってロックを解放しない場合、待機中のスレッドは待機を放棄し、他の処理に変更することができ、実行時間の非常に高い同期ブロックの処理に役立ちます.synchronizedによって生成される反発ロックを待つと、ブロックされ続け、中断されません.
フェアロックを実現可能:複数のスレッドが同じロックを待機している場合、フェアロックではなく申請ロックの時間順に並んで待機する必要があります.これは保証されません.ロックが解放されると、ロックを待機しているスレッドのいずれかがロックを取得する機会があります.synchronizedのロック時は非フェアロックであり、ReentrantLockはデフォルトでも非フェアロックであるが、構造方法ReentrantLock(ture)によってフェアロックの使用を要求することができる.ロックは複数の条件をバインドできます:ReentrantLockオブジェクトは複数のConditionオブジェクト(条件変数または条件キュー)を同時にバインドできますが、synchronizedでは、ロックオブジェクトのwait()とnotify()またはnotifyAll()メソッドは1つの隠し条件を実現できますが、1つ以上の条件に関連付ける場合は、1つのロックを追加しなければなりません.ReentrantLockはそうする必要はなく、newCondition()メソッドを複数回呼び出すだけです.さらに、現在のスレッドが通知するスレッド(すなわち、Conditionオブジェクトにバインドされた他のスレッド)を、Conditionオブジェクトをバインドすることによって判断することもできる.割り込み可能ロック
ReetrantLockには、割り込みロックと応答割り込みロックを無視する2つのロックがあります.割り込みロックを無視することはsynchronizedによって実現される反発ロックと同様に、割り込みに応答することはできず、応答割り込みロックは割り込みに応答することができる.
あるスレッドAがロック中のコードを実行している場合、別のスレッドBがロックの取得を待っている場合、待ち時間が長すぎるため、スレッドBが待ちたくない可能性があります.他のことを先に処理したい場合は、自分を中断させるか、他のスレッドで中断させることができます.ReetrantLockが割り込みロックを無視することを提供している場合は、割り込みを無視することはできません.スレッドBを待ち続けるのではなく、ReetrantLockが応答割り込みロックを提供している場合、割り込みを処理し、スレッドBに待機を放棄させ、他のことを処理します.
応答割り込みロックを取得する一般的な形式は、次のとおりです.
ReentrantLock lock = new ReentrantLock();
...........
lock.lockInterruptibly();//
try {
//
// ,
// return ,
}finally{
lock.unlock(); // finally
}
ここには、分析中断のサンプルコード(ネット上から抜粋)があります.
synchronizedで反発ロックの待機を中断すると、次の例のようにスレッドは機能しません.
public class Buffer {
private Object lock;
public Buffer() {
lock = this;
}
public void write() {
synchronized (lock) {
long startTime = System.currentTimeMillis();
System.out.println(" buff …");
for (;;)//
{
if (System.currentTimeMillis()
- startTime > Integer.MAX_VALUE) {
break;
}
}
System.out.println(" ");
}
}
public void read() {
synchronized (lock) {
System.out.println(" buff ");
}
}
public static void main(String[] args) {
Buffer buff = new Buffer();
final Writer writer = new Writer(buff);
final Reader reader = new Reader(buff);
writer.start();
reader.start();
new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
for (;;) {
// 5
if (System.currentTimeMillis()
- start > 5000) {
System.out.println(" , ");
reader.interrupt(); //
break;
}
}
}
}).start();
// “ ” , , ,
// , , , 21 T_T , ,
// , 。 ,ReentrantLock ,
// “ ” , 。 Buffer , BufferInterruptibly , 。
}
}
class Writer extends Thread {
private Buffer buff;
public Writer(Buffer buff) {
this.buff = buff;
}
@Override
public void run() {
buff.write();
}
}
class Reader extends Thread {
private Buffer buff;
public Reader(Buffer buff) {
this.buff = buff;
}
@Override
public void run() {
buff.read();//
System.out.println(" ");
}
}
実行結果は次のとおりです.
我々は長い間待っていたが,その後も出力がなく,リードスレッドの反発ロックに対する待機が中断されていないこと,すなわち,ユーザがロックを食べてリードスレッドの中断に応答していないことを示した.
上記のコードのsynchronizedの反発ロックをReentrantLockの応答割り込みロックに変更します.つまり、次のコードに変更します.
import java.util.concurrent.locks.ReentrantLock;
public class BufferInterruptibly {
private ReentrantLock lock = new ReentrantLock();
public void write() {
lock.lock();
try {
long startTime = System.currentTimeMillis();
System.out.println(" buff …");
for (;;)//
{
if (System.currentTimeMillis()
- startTime > Integer.MAX_VALUE) {
break;
}
}
System.out.println(" ");
} finally {
lock.unlock();
}
}
public void read() throws InterruptedException {
lock.lockInterruptibly();// ,
try {
System.out.println(" buff ");
} finally {
lock.unlock();
}
}
public static void main(String args[]) {
BufferInterruptibly buff = new BufferInterruptibly();
final Writer2 writer = new Writer2(buff);
final Reader2 reader = new Reader2(buff);
writer.start();
reader.start();
new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
for (;;) {
if (System.currentTimeMillis()
- start > 5000) {
System.out.println(" , ");
reader.interrupt(); //
break;
}
}
}
}).start();
}
}
class Reader2 extends Thread {
private BufferInterruptibly buff;
public Reader2(BufferInterruptibly buff) {
this.buff = buff;
}
@Override
public void run() {
try {
buff.read();// ,
} catch (InterruptedException e) {
System.out.println(" ");
}
System.out.println(" ");
}
}
class Writer2 extends Thread {
private BufferInterruptibly buff;
public Writer2(BufferInterruptibly buff) {
this.buff = buff;
}
@Override
public void run() {
buff.write();
}
}
実行結果は次のとおりです.
結果から,試行中断後にcatch文ブロックの内容が出力され,後続の「読み取り終了」も出力され,スレッドの反発ロックに対する待機が中断されたこと,すなわち,この反発ロックがリードスレッドの中断に応答したことを示す.