Javaで排他制御などの処理を行いたいときに使えるクラス一覧です。
通常はsynchronizedによるロックで事足りる場合が多いですが、リアクティブな実装の時のロックや、効率を考えたロックを取る場合など、少し複雑なロックを実装する際に使えるクラスの紹介です。
Object lockObj = new Object();
synchronized(lockObj){
// クリティカルセッション
}
※ ロックオブジェクトは、全スレッドで使いまわします。
java.concurrent.atomic.AtomicInteger,AtomicLong, etc
値の増減をアトミックに行えるクラスです。カウンターを実装したいときに便利です。
他にも、java.concurrent.atomicに幾つか同様のクラスがあります。
import java.concurrent.atomic.AtomicInteger;
AtomicInteger counter = new AtomicInteger(0) // 初期値を渡す
int five = counter.addAndGet(5);
java.concurrent.CountDownLatch
値が0になるまでブロックすることの出来るクラスです。複数の処理を投げて、全ての処理が行われるまでブロックする場合などに使用出来ます。
import java.concurrent.CountDownLatch;
CountDownLatch latch = new CountDownLatch(10); // latchのカウント初期値
latch.await(); // latchの残りカウントが0になるまでブロックされます
// 別スレッドで
latch.countDown(); // 残りカウントを1減らす。これが0になるとawait()のブロックが解除される
java.concurrent.Semaphore
synchronizedによるロックは、ただ1つの処理のみがロックを取得できますが、Semaphoreは同時に2つ以上の処理がロックを取得できるように設定できます。むしろ、通常のロックが、Semaphoreの同時ロック取得数が1の場合ということになります。
リソースが限られているものの、いくつかは処理が同時に走っても問題ないような場合に使用します。
import java.concurrent.Semaphore;
Semaphore semaphore = new Semaphore(3); // 最大3個まで同時にロックを取れる
semaphore.permit(); // permissionを1つ取得する。取得できるまではブロックされる
try{
//クリティカルセッション
}finally{
semaphore.release(); // 取得したpermissionを1つ開放します
}
// or
if(semaphore.tryAquire()){ // permissionを取れるか試し、即座にその結果を返す
try{
// クリティカルセッション
} finally {
semaphore.release();
}
}
java.concurrent.CycleBarrier
N個全てのスレッドの処理が終わるまで各スレッドをブロックし、全てのスレッドが終了したらbarrierActionを最後に一度だけ実行するクラスです。1度しか実行しないで良いなら、CountDownLatchでも同様のことが出来ます。
import java.concurrent.CycleBarrier;
int N = 10; // 同時に実行したいスレッド数
Runnable barrierAction = ...// 最後に一度だけ実行したい処理
CycleBarrier barrier = new CycleBarrier(N,barrierAction);
// 各スレッドで
{
// 処理
// 他のスレッドが終了するまで、ここでブロックされる。
// これがN回呼ばれると、barrierActionが実行され、その後awaitのブロックが解除される
barrier.await();
// 後処理
}
java.concurrent.locks.Lock, Condition
Lockは通常のロック機能を提供するinterfaceです。interfaceなので、実装切り替えは出来ますが、通常はすでに用意されているReentrantLockを使うことになると思います。
Conditionは、Lockされる対象の状態が2状態以上ある複雑な場合に上手くロックを取れるように出来ます。
つまり、ロック対象が,A,B,2つの状態を取り、処理1は、状態Aのときは処理出来るが、状態Bのときは状態A
になるまで待つ必要があり、処理2は,状態Bのときは処理出来るが、状態Aのときは状態Bになるまで待つといった場合に使うことになります。
import java.concurrent.locks.{Lock, Condition, ReentrantLock};
Lock lock = new ReentrantLock();
lock.lock(); // ロックを取得するまで待ちます。 lock.tryLock()もあります。
try{
// クリティカルセッション
} finally{
lock.unlock();
}
import java.concurrent.locks.{Lock, Condition};
Lock lock = = new ReentrantLock();
Condition aCondition = lock.newCondition();
Condition bCondition = lock.newCondition();
string state = "A";
// 処理1
{
lock.lock();
try{
while(state.equals("B")){
// Condition#await()を呼ぶと、Conditionの親のLockのロック取得を一旦開放し、Condition#signal()が呼ばれるまでブロックします。
// 処理再開時に再度Lockを取ってくれます。
aCondition.await();
}
// クリティカルセッション
} finally{
lock.unlock();
}
}
// 処理2
{
lock.lock();
try{
while(state.equals("A")){
bCondition.await();
}
// クリティカルセッション
} finally{
lock.unlock();
}
// 状態を変更する処理
{
lock.lock();
if(state.equals("A")){
state = "B";
bCondition.signalAll();// Signalを送ることで対応するConditionでawaitしていた処理が再開する。
} else {
state = "A";
aCondition.signalAll();
}
lock.unlock();
}