Получение потока для приостановки - Thread.wait () / Thread.notify () - PullRequest
4 голосов
/ 24 января 2012

Я пытаюсь понять, как работают потоки, и я написал простой пример, в котором я хочу создать и запустить новый поток, поток, отображать числа от 1 до 1000 в основном потоке, возобновить дополнительный поток,и отображать числа от 1 до 1000 во вторичном потоке.Когда я пропускаю Thread.wait () / Thread.notify (), он ведет себя как ожидалось, оба потока отображают несколько чисел одновременно.Когда я добавляю эти функции, по какой-то причине номера основного потока печатаются вторыми, а не первыми.Что я делаю неправильно?

public class Main {

    public class ExampleThread extends Thread {

        public ExampleThread() {
            System.out.println("ExampleThread's name is: " + this.getName());
        }

        @Override
        public void run() {         
            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();

        synchronized(t) {
            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();
        }
    }
}

Ответы [ 3 ]

11 голосов
/ 24 января 2012

Вы неправильно понимаете, как работает 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();
        }
    }
}

В этом примере выходные данные полностью детерминированы.Вот что происходит:

  1. Основной поток создает новый объект t.
  2. Основной поток получает блокировку на мониторе t.
  3. Запуск основного потокапоток t.
  4. (это может происходить в любом порядке)
    1. Запускается вторичный поток, но поскольку основной поток по-прежнему владеет монитором t, вторичный поток не может продолжить и должен ждать(потому что его первый оператор - synchronized (this), , а не , потому что это происходит с be t объектом - все блокировки, уведомления и ожидания также могут быть полностью выполнены для объектане связанный ни с одним из 2 потоков с одинаковым результатом.
    2. Первичный поток продолжается, возвращается к части t.wait() и приостанавливает его выполнение, освобождая монитор t, с которым он синхронизировался.
  5. Вторичный поток получает право собственности на монитор t.
  6. Вторичный поток вызывает t.notify(), пробуждая основной поток. Тем не менее основной поток не может продолжаться, поскольку дополнительный поток все еще удерживается.владение t монитором.
  7. Вторичный поток вызывает t.wait(), приостанавливает его выполнение и освобождает монитор t.
  8. Первичный поток может, наконец, продолжиться, так как монитор tтеперь доступен.
  9. Первичный поток получает право собственности на монитор t, но сразу освобождает его.
  10. Первичный поток выполняет подсчет числа.
  11. Первичный поток снова получаетправо собственности на монитор t.
  12. Основной поток вызывает t.notify(), пробуждая дополнительный поток.Вторичный поток еще не может продолжаться, поскольку основной поток все еще содержит монитор t.
  13. Основной поток освобождает монитор t и завершает работу.
  14. Вторичный поток получает владение t монитор, но сразу его освобождает.
  15. Вторичный поток выполняет подсчет чисел, а затем завершается.
  16. Завершается все приложение.

Как вы можетевидите, даже в таком обманчиво простом сценарии многое происходит.

2 голосов
/ 24 января 2012

Вам повезло, что ваша программа вообще не работает.

Когда вы звоните t.wait(), ваши основные темы останавливаются и ждут уведомления в течение неопределенного времени.

Это никогда не получится, но я верю, что оно пробуждается ложным пробуждением , когда заканчивается вторичный поток. (Прочтите здесь о том, что такое ложное пробуждение).

2 голосов
/ 24 января 2012

ExampleThread не wait() или notify() и не synchronized ни для чего. Поэтому он будет работать всякий раз, когда это возможно, без какой-либо координации с другими потоками.

Основной поток ожидает уведомления, которое никогда не приходит (это уведомление должно быть отправлено другим потоком). Я предполагаю, что, когда ExampleThread умирает, основной поток "пробуждается" и завершается.

Поток, который должен ждать завершения другого, должен выполнить вызов wait() внутри цикла, который проверяет условие:

class ExampleThread extends Thread {

  private boolean ready = false;

  synchronized void ready() { 
    ready = true; 
    notifyAll();
  }

  @Override
  public void run() {
    /* Wait to for readiness to be signaled. */
    synchronized (this) {
      while (!ready)
        try {
          wait();
        } catch(InterruptedException ex) {
          ex.printStackTrace();
          return; /* Interruption means abort. */
        }
    }
    /* Now do your work. */
    ...

Тогда в вашей основной теме:

ExampleThread t = new ExampleThread();
t.start();
/* Do your work. */
...
/* Then signal the other thread. */
t.ready();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...