第54回 ロック編 synchronizedによるロック
今回のテーマは「synchronized」です。
Javaのオブジェクトにはロックが1つ用意されています。 スレッドはsynchronizedメソッドまたはsynchronized文を実行する場合に、そのオブジェクトのロックを取得しようとします。 スレッドがロックを取得しようとする場合において、他のスレッドがそのロックを取得しているときは、ロック待ちの状態になります。 したがって、1度に同じオブジェクトのsynchronizedメソッドまたはsynchronized文の処理を実行できるのは1つのスレッドのみです。 このような排他処理において、他のスレッドの排他処理を待つといったことを実現するために、waitメソッド、notifyメソッド、notifyAllメソッドがあります。
これらのメソッドを実行するには、オブジェクトのロックを取得している必要があります。 waitメソッドはスレッドを待機させ、オブジェクトのロックも開放されます。 notifyメソッドは待機中のスレッドを1つ再開します。 notifyAllメソッドは待機中のすべてのスレッドを再開します。 つまり、オブジェクトのwaitメソッドを実行したスレッドは、他のスレッドがそのオブジェクトのnotifyメソッドまたはnotifyAllメソッドを実行するまで待機するということです また、再開したスレッドは排他処理を継続するために、そのオブジェクトのロックを取得しようとします。
次のサンプルコードは容量制限のあるBlockingQueueを模したものです。(J2SE5.0以上対応)
import java.util.LinkedList; import java.util.Queue; public class MyBlockingQueue<E> { private Queue<E> queue = new LinkedList<E>(); private int capacity; public MyBlockingQueue(int capacity) { this.capacity = capacity; } public void put(E e) throws InterruptedException { synchronized (this) { while (queue.size() >= capacity) { wait(); } queue.offer(e); notifyAll(); } } public E take() throws InterruptedException { E e = null; synchronized (this) { while (queue.isEmpty()) { wait(); } e = queue.poll(); notifyAll(); } return e; } }
MyBlockingQueueクラスは、同じインスタンスに対して複数のスレッドから要素の追加と取得を排他的に行うことを想定しています。 putメソッドはキューに要素を追加するメソッドです。 takeメソッドはキューから要素を取得するメソッドです。
スレッドはputメソッドを実行した場合において、キューの要素数がcapacityに達しているときは、waitメソッドを実行し待機します。 このスレッドは、他のスレッドがtakeメソッドを実行し要素取得後にnotifyAllメソッドが実行されることによって、再開することが可能です。 notifyAllメソッドで待機中のすべてのスレッドを再開させようとしているのは、notifyメソッドでは必ずしも追加操作を待機中のスレッドが再開するとは限らないからです。
スレッドはtakeメソッドを実行した場合において、キューが空のときは、waitメソッドを実行し待機します。 このスレッドは、他のスレッドがputメソッドを実行し要素追加後にnotifyAllメソッドが実行されることによって、再開することが可能です。 notifyAllメソッドで待機中のすべてのスレッドを再開させようとしているのは、notifyメソッドでは必ずしも取得操作を待機中のスレッドが再開するとは限らないからです。
複数のスレッドからのキューに対する操作を制限するためのMyBlockingQueueインスタンスのロック、waitメソッドによるロックの開放、notifyAllメソッドによるスレッドの再開がポイントです。