マルチスレッドアプリケーションの2つのロック
2902 ワード
Javaマルチスレッド操作では、データの同期のために同期が必要な操作をロック処理します.例えば、次のようなカウンタです.
マルチスレッド環境において、データ操作が混乱することを回避するために、
Java言語のキーワードは、オブジェクトとメソッドまたはコードブロックにロックをかけるために使用できます.メソッドまたはコードブロックをロックすると、同じ時点で最大1つのスレッドだけがこのセグメントコードを実行します.2つの同時スレッドが同じオブジェクトobjectのこのロック同期コードブロックにアクセスすると、1時間に1つのスレッドしか実行できません.別のスレッドは、現在のスレッドがこのコードブロックを実行するまで待たなければなりません.しかしながら、あるスレッドがobjectの1つのロックコードブロックにアクセスする場合、別のスレッドは、object内の非ロックコードブロックにアクセスすることができる
あるスレッドが臨界リソース(ここではCounter対応のインスタンスオブジェクト)を占有すると、残りのスレッドは、臨界リソースを占有するロックが解放されるまでブロックされます.ここでは、ブロックされたスレッドが中断され、ブロックされたスレッドが再実行されると、スレッドコンテキストの切り替えが必要になるというスレッドオーバーヘッドの問題が発生しました.スレッドコンテキスト切替は、現在のオペレーティングシステムでは比較的時間のかかる操作です.解決策:
CASには3つのオペランド、メモリ値V、古い予想値A、修正する新しい値Bがあります.そして、メモリ値Vは、予想値Aとメモリ値Vとが同時にBに変更された場合にのみ、何もしない.CAS操作は現代のオペレーティングシステムにおいて原子文である:コンピュータ命令によって完成することができる.CAS対応の動作をCAS(V,A,B)とし、Vメモリ対応の値がAの場合のみ、Vメモリの値をBに変更する.
1つのスレッドの失敗または保留は、他のスレッドの失敗または保留に影響を与えるべきではないアルゴリズムです.
現代のCPUは,共有データを自動的に更新し,他のスレッドの干渉を検出できる特殊な命令を提供しているが,compareAndSet()はこれらをロックの代わりに用いている.
AtomicIntegerを取り出して,ロックなしでデータの正確性を調べる.
まず,ロックのないメカニズムでは
これにより、変数の値が取得されたときに直接読み込むことができます.
そして++iがどうやってやったのか見てみましょう.
ここではCASオペレーションを採用し,メモリからデータを読み出すたびに+1後の結果とCASオペレーションを行い,成功すれば結果を返し,そうでなければ成功するまで再試行する.
一方、
全体的なプロセスは,CPUのCAS命令を利用しながらJNIを用いてJavaの非ブロックアルゴリズムを完成させる.他の原子操作は類似の特性を用いて行われた.
class Counter
{
private int i=0;
public synchronized int get()
{
return this.i;
}
public synchronized void increase()
{
this.i++;
}
}
マルチスレッド環境において、データ操作が混乱することを回避するために、
synchronized
のキーワードを用いる.synchronized
Java言語のキーワードは、オブジェクトとメソッドまたはコードブロックにロックをかけるために使用できます.メソッドまたはコードブロックをロックすると、同じ時点で最大1つのスレッドだけがこのセグメントコードを実行します.2つの同時スレッドが同じオブジェクトobjectのこのロック同期コードブロックにアクセスすると、1時間に1つのスレッドしか実行できません.別のスレッドは、現在のスレッドがこのコードブロックを実行するまで待たなければなりません.しかしながら、あるスレッドがobjectの1つのロックコードブロックにアクセスする場合、別のスレッドは、object内の非ロックコードブロックにアクセスすることができる
あるスレッドが臨界リソース(ここではCounter対応のインスタンスオブジェクト)を占有すると、残りのスレッドは、臨界リソースを占有するロックが解放されるまでブロックされます.ここでは、ブロックされたスレッドが中断され、ブロックされたスレッドが再実行されると、スレッドコンテキストの切り替えが必要になるというスレッドオーバーヘッドの問題が発生しました.スレッドコンテキスト切替は、現在のオペレーティングシステムでは比較的時間のかかる操作です.解決策:
CAS
を使用して実装CAS (CompareAndSwap)
CASには3つのオペランド、メモリ値V、古い予想値A、修正する新しい値Bがあります.そして、メモリ値Vは、予想値Aとメモリ値Vとが同時にBに変更された場合にのみ、何もしない.CAS操作は現代のオペレーティングシステムにおいて原子文である:コンピュータ命令によって完成することができる.CAS対応の動作をCAS(V,A,B)とし、Vメモリ対応の値がAの場合のみ、Vメモリの値をBに変更する.
非ブロックアルゴリズム(nonblocking algorithms)
1つのスレッドの失敗または保留は、他のスレッドの失敗または保留に影響を与えるべきではないアルゴリズムです.
現代のCPUは,共有データを自動的に更新し,他のスレッドの干渉を検出できる特殊な命令を提供しているが,compareAndSet()はこれらをロックの代わりに用いている.
AtomicIntegerを取り出して,ロックなしでデータの正確性を調べる.
private volatile int value;
まず,ロックのないメカニズムでは
volatile
原語を用いて,スレッド間のデータが可視(共有)であることを保証する必要があるとは思わなかった.これにより、変数の値が取得されたときに直接読み込むことができます.
public final int get() {
return value;
}
そして++iがどうやってやったのか見てみましょう.
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
ここではCASオペレーションを採用し,メモリからデータを読み出すたびに+1後の結果とCASオペレーションを行い,成功すれば結果を返し,そうでなければ成功するまで再試行する.
一方、
compareAndSet
は、JNIを利用してCPUコマンドの動作を完了する.public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
全体的なプロセスは,CPUのCAS命令を利用しながらJNIを用いてJavaの非ブロックアルゴリズムを完成させる.他の原子操作は類似の特性を用いて行われた.
CAS
を使用すると、Counterは次のように変更できます.class Counter
{
private AtomicInteger i=new AtomicInteger(0);
public Integer get()
{
return i.get();
}
public void increase()
{
for(;;)
{
int value=i.get();
if(i.compareAndSet(value,value+1))
{
break;
}
}
}
}