Вы неправильно понимаете, как работает wait
/ notify
.wait
не не блокирует поток, в котором он вызывается;он блокирует текущий поток до тех пор, пока уведомление не будет вызвано для того же объекта (поэтому, если у вас есть потоки A и B и, находясь в потоке A, называемые B.wait (), это будетостановите поток A и не поток B - до тех пор, пока B.notify () не вызывается).
Итак, в вашем конкретном примере, если вы хотите, чтобы основной поток выполнялся первым,вам нужно поместить wait () во вторичный поток.Например:
public class Main {
public class ExampleThread extends Thread {
public ExampleThread() {
System.out.println("ExampleThread's name is: " + this.getName());
}
@Override
public void run() {
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {
}
}
for(int i = 1; i < 1000; i++) {
System.out.println(Thread.currentThread().getName());
System.out.println(i);
}
}
}
public static void main(String[] args) {
new Main().go();
}
public void go() {
Thread t = new ExampleThread();
t.start();
for(int i = 1; i < 1000; i++) {
System.out.println(Thread.currentThread().getName());
System.out.println(i);
}
synchronized(t) {
t.notify();
}
}
}
Однако даже этот код может работать не так, как вы хотите.В сценарии, когда основной поток попадает в часть notify () до , вторичный поток имел возможность добраться до части wait () (маловероятно в вашем случае, но все же возможно - вы можете наблюдать это, еслиВы помещаете Thread.sleep в начало вторичного потока), вторичный поток никогда не будет пробужден.Следовательно, самый безопасный метод будет выглядеть примерно так:
public class Main {
public class ExampleThread extends Thread {
public ExampleThread() {
System.out.println("ExampleThread's name is: " + this.getName());
}
@Override
public void run() {
synchronized (this) {
try {
notify();
wait();
} catch (InterruptedException e) {
}
}
for(int i = 1; i < 1000; i++) {
System.out.println(Thread.currentThread().getName());
System.out.println(i);
}
}
}
public static void main(String[] args) {
new Main().go();
}
public void go() {
Thread t = new ExampleThread();
synchronized (t) {
t.start();
try {
t.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int i = 1; i < 1000; i++) {
System.out.println(Thread.currentThread().getName());
System.out.println(i);
}
synchronized(t) {
t.notify();
}
}
}
В этом примере выходные данные полностью детерминированы.Вот что происходит:
- Основной поток создает новый объект
t
. - Основной поток получает блокировку на мониторе
t
. - Запуск основного потокапоток
t
. - (это может происходить в любом порядке)
- Запускается вторичный поток, но поскольку основной поток по-прежнему владеет монитором
t
, вторичный поток не может продолжить и должен ждать(потому что его первый оператор - synchronized (this)
, , а не , потому что это происходит с be t
объектом - все блокировки, уведомления и ожидания также могут быть полностью выполнены для объектане связанный ни с одним из 2 потоков с одинаковым результатом. - Первичный поток продолжается, возвращается к части
t.wait()
и приостанавливает его выполнение, освобождая монитор t
, с которым он синхронизировался.
- Вторичный поток получает право собственности на монитор
t
. - Вторичный поток вызывает
t.notify()
, пробуждая основной поток. Тем не менее основной поток не может продолжаться, поскольку дополнительный поток все еще удерживается.владение t
монитором. - Вторичный поток вызывает
t.wait()
, приостанавливает его выполнение и освобождает монитор t
. - Первичный поток может, наконец, продолжиться, так как монитор
t
теперь доступен. - Первичный поток получает право собственности на монитор
t
, но сразу освобождает его. - Первичный поток выполняет подсчет числа.
- Первичный поток снова получаетправо собственности на монитор
t
. - Основной поток вызывает
t.notify()
, пробуждая дополнительный поток.Вторичный поток еще не может продолжаться, поскольку основной поток все еще содержит монитор t
. - Основной поток освобождает монитор
t
и завершает работу. - Вторичный поток получает владение
t
монитор, но сразу его освобождает. - Вторичный поток выполняет подсчет чисел, а затем завершается.
- Завершается все приложение.
Как вы можетевидите, даже в таком обманчиво простом сценарии многое происходит.