第56回 ロック編 Conditionインターフェース
今回のテーマは「Condition」です。
前々回はsynchronizedによる排他制御と、waitメソッド、notifyAllメソッドによるスレッドの待ち合わせについて書きました。 notfiyAllメソッドを使用したのは、特定の状態で待機したスレッドのみを再開する術がないからです。 そのため、再開しなくてもよいスレッドが再開してしまう可能性がありました。 しかし、Lockによる排他制御を行う場合、Connditionにより状態を区別し、特定の状態で待機したスレッドのみを再開することができます。 Conditionインスタンスは、内在的にLockにバインドされており、newConditionメソッドで取得します。
次のサンプルコードは容量制限のあるBlockingQueueを模したものです。 排他制御にLockを使用していることが前々回との違いです。(J2SE5.0以上対応)
import java.util.LinkedList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.Queue; public class MyBlockingQueue<E> { private Queue<E> queue = new LinkedList<E>(); private int capacity; private Lock lock = new ReentrantLock(); private Condition putCondition = lock.newCondition(); private Condition takeCondition = lock.newCondition(); public MyBlockingQueue(int capacity) { this.capacity = capacity; } public void put(E e) throws InterruptedException { lock.lock(); try { while (queue.size() >= capacity) { putCondition.await(); } queue.offer(e); takeCondition.signal(); } finally { lock.unlock(); } } public E take() throws InterruptedException { E e = null; lock.lock(); try { while (queue.isEmpty()) { takeCondition.await(); } e = queue.poll(); putCondition.signal(); } finally { lock.unlock(); } return e; } }
前々回のようにwaitメソッドを使用した場合、追加操作を待機したスレッドも取得操作を待機したスレッドも同じ待機セットに含まれます。 これでは、notifyメソッドを使用した場合、どちらのスレッドが再開するかはわかりません。 しかし、これらのスレッドを別々の待機セットに含めれば、どちらかのスレッドだけを再開することができます。 それは2つのConditionインスタンスを使用して実現できます。
サンプルコードでは、追加操作の待機セット(putCondition)と取得操作の待機セット(takeCondition)を用意しました。 putConditionのsignalメソッドは、putConditionのawaitメソッドにより待機したスレッドを1つ再開します。 takeConditionのsignalメソッドは、takeConditionのawaitメソッドにより待機したスレッドを1つ再開します。