Шрифт:
Методы wait, notify, notifyAll класса Object
Наконец, перейдем к рассмотрению трех методов класса Object, завершая описание механизмов поддержки многопоточности в Java.
Каждый объект в Java имеет не только блокировку для synchronized блоков и методов, но и так называемый wait-set, набор потоков исполнения. Любой поток может вызвать метод wait любого объекта и таким образом попасть в его wait-set. При этом выполнение такого потока приостанавливается до тех пор, пока другой поток не вызовет у этого же объекта метод notifyAll, который пробуждает все потоки из wait-set. Метод notify пробуждает один случайно выбранный поток из данного набора.
Однако применение этих методов связано с одним важным ограничением. Любой из них может быть вызван потоком у объекта только после установления блокировки на этот объект. То есть либо внутри synchronized -блока с ссылкой на этот объект в качестве аргумента, либо обращения к методам должны быть в синхронизированных методах класса самого объекта. Рассмотрим пример:
public class WaitThread implements Runnable {
private Object shared;
public WaitThread(Object o) {
shared=o;
}
public void run {
synchronized (shared) {
try {
shared.wait;
}
catch (InterruptedException e) {
}
System.out.println("after wait");
}
}
public static void main(String s[]) {
Object o = new Object;
WaitThread w = new WaitThread(o);
new Thread(w).start;
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
}
System.out.println("before notify");
synchronized (o) {
o.notifyAll;
}
}
}
Результатом программы будет:
before notify
after wait
Обратите внимание, что метод wait, как и sleep, требует обработки InterruptedException, то есть его выполнение также можно прервать методом interrupt.
В заключение рассмотрим более сложный пример для трех потоков:
public class ThreadTest implements Runnable {
final static private Object shared=new Object;
private int type;
public ThreadTest(int i) {
type=i;
}
public void run {
if (type==1 || type==2) {
synchronized (shared) {
try {
shared.wait;
}
catch (InterruptedException e) {
}
System.out.println("Thread "+type+" after wait");
}
}
else {
synchronized (shared) {
shared.notifyAll;
System.out.println("Thread "+type+" after notifyAll");
}
}
}
public static void main(String s[]) {
ThreadTest w1 = new ThreadTest(1);
new Thread(w1).start;
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
}
ThreadTest w2 = new ThreadTest(2);
new Thread(w2).start;
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
}
ThreadTest w3 = new ThreadTest(3);
new Thread(w3).start;
}
}
Пример 12.5.
Результатом работы программы будет:
Thread 3 after notifyAll
Thread 1 after wait
Thread 2 after wait
Пример 12.6.
Рассмотрим, что произошло. Во-первых, был запущен поток 1, который тут же вызвал метод wait и приостановил свое выполнение. Затем то же самое произошло с потоком 2. Далее начинает выполняться поток 3.
Сразу обращает на себя внимание следующий факт. Еще поток 1 вошел в synchronized -блок, а стало быть, установил блокировку на объект shared. Но, судя по результатам, это не помешало и потоку 2 зайти в synchronized -блок, а затем и потоку 3. Причем, для последнего это просто необходимо, иначе как можно "разбудить" потоки 1 и 2?
Можно сделать вывод, что потоки, прежде чем приостановить выполнение после вызова метода wait, отпускают все занятые блокировки. Итак, вызывается метод notifyAll. Как уже было сказано, все потоки из wait-set возобновляют свою работу. Однако чтобы корректно продолжить исполнение, необходимо вернуть блокировку на объект, ведь следующая команда также находится внутри synchronized -блока!
Получается, что даже после вызова notifyAll все потоки не могут сразу возобновить работу. Лишь один из них сможет вернуть себе блокировку и продолжить работу. Когда он покинет свой synchronized -блок и отпустит объект, второй поток возобновит свою работу, и так далее. Если по какой-то причине объект так и не будет освобожден, поток так никогда и не выйдет из метода wait, даже если будет вызван метод notifyAll. В рассмотренном примере потоки один за другим смогли возобновить свою работу.