2015-08-13 4 views
1

Согласно тому, что я понял, когда я использую синхронизированный блок, он получает блокировку объекта и освобождает его, когда выполняется блок кода. В следующем кодесинхронизированный объект блокировки блоков и wait/notify

public class WaitAndNotify extends Thread{ 

    long sum; 

    public static void main(String[] args) { 
     WaitAndNotify wan = new WaitAndNotify(); 
     //wan.start(); 
     synchronized(wan){ 
      try { 
       wan.wait(); 
      } catch (InterruptedException ex) { 
       Logger.getLogger(WaitAndNotify.class.getName()).log(Level.SEVERE, null, ex); 
      } 
      System.out.println("Sum is : " + wan.sum); 
     } 
    } 

    @Override 
    public void run(){ 
     synchronized(this){ 
      for(int i=0; i<1000000; i++){ 
       sum = sum + i; 
      } 
      notify(); 
     } 

    } 
} 

Что произойдет, если синхронизированный блок внутри метода запуска сначала получит блокировку? Затем синхронизированный блок внутри основного метода должен ждать (не из-за ожидания(), потому что другой поток получил блокировку). После выполнения метода run основной метод не будет вводить свой синхронизированный блок и ждать уведомления, которое он никогда не получит? Что я тут неправильно понял?

+0

Отойдите от этих базовых API до расширенных API, таких как java.util.concurrent.Executors и ExecutorService.Посмотрите на образец кода: http://examples.javacodegeeks.com/core-java/util/concurrent/executorservice/java-executorservice-example-tutorial/ –

+1

@ sunrise76, классы в 'java.util.concurrent' не являются" более продвинутые: «Они работают на более высоких уровнях абстракции. В коде для написания кода devloper обязательно должны использоваться инструменты более высокого уровня, но _student_ будет хорошо понимать примитивы, на которых создаются эти инструменты более высокого уровня. Так же, как люди, которые изучили ассемблер, принимают более обоснованные решения, когда они пишут на языке более высокого уровня, поэтому люди, которые узнали о мьютексах и переменных условий и атомных операциях, лучше используют очереди и пулы потоков и т. Д. –

+0

Ваше понимание верно. 'foo.notify()' ничего не делает, если ни один другой поток не блокируется в вызове 'foo.wait()' в тот же момент. –

ответ

1

Да, вы можете выполнить notify() до появления wait(), вызвав подвесную нить, поэтому вам нужно быть осторожным, чтобы этого не произошло.

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

1

Вы не увидите проблему «ожидание» здесь, потому что вы вызываете версию wait() с таймаутом; поэтому через 5 секунд он возвращается, даже если он не получает уведомление. В ожидании вызова wait() действительно может возникнуть проблема, которую вы описываете.

+0

Прошу прощения, я забыл снять 5-ю часть. если это wait(), возможно ли, что notify() запускается до wait()? –

+0

Да. Если это произойдет, ваш основной поток выполнения зависает. –

1

У вас есть два потока: поток WaitAndNotify (WAN) и основной поток выполнения Java. Оба соперничают за одну и ту же блокировку.

Если WAN-поток сначала получает блокировку, основной поток будет заблокирован. Если вы находитесь в заблокированном состоянии, это НЕ то же самое, что находиться в состоянии ожидания. Поток в состоянии ожидания будет ждать уведомления, прежде чем двигаться вперед. Нить в заблокированном состоянии будет активно пытаться получить блокировку, когда она станет доступной (и продолжать пытаться, пока это не произойдет).

Предполагая, что метод run выполняется нормально, он будет вызывать notify(), что не будет иметь никакого эффекта, потому что другие нити в настоящее время не находятся в состоянии ожидания. Даже если бы они были, WAN все еще удерживает блокировку, пока не выйдет из синхронизированного блока кода. После того, как WAN выйдет из блока, THEN Java уведомит ожидающий поток (если он есть, а его нет).

На этом этапе основной поток выполнения теперь получает блокировку (она больше не заблокирована) и переходит в состояние ожидания. Теперь вы использовали версию ожидания, которая будет ждать до 5000 миллисекунд, прежде чем продолжить. Если вы использовали версию ванили (wait()), она будет ждать вечно, потому что никакой другой процесс не будет уведомлять об этом.

2

wait() неявно выходит соответствующий монитор временно и повторно вводит его по возвращении:

См wait()

Текущий поток должен владеть монитором данного объекта. Линия передает владельца этого монитора и ждет, пока другой поток не сообщит потоки, ожидающие на мониторе этого объекта, чтобы проснуться либо через вызов метода уведомления, либо метода уведомления. Поток затем ждет, пока он не сможет повторно получить право собственности на монитор и возобновит исполнение.

Вот почему и как эта синхронизация вообще работает.

+0

Пока [wait()] (https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--) выпускает монитор объекта, [notify()] (https : //docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notify--)/[notifyAll()] (https://docs.oracle.com/javase/8/ docs/api/java/lang/Object.html # notifyAll--). [«Пробужденные потоки не смогут действовать до тех пор, пока текущий поток не удалит блокировку этого объекта».] (Https://docs.oracle.com/javase/8/docs/api/java/lang/Object. HTML # notifyAll--) – naaz

1

Вот версия примера программы, измененная для введения цикла, который проверяет переменную условия. Таким образом, можно избежать плохих предположений о положении вещей после того, как нить повторных получает блокировку после пробуждения от ожидания, и нет никакого порядка зависимости между двумя потоками:

public class W extends Thread { 
    long sum; 
    boolean done; 

    public static void main(String[] args) throws InterruptedException { 
     W w = new W(); 
     w.start(); 
     synchronized(w) { 
      while (!w.done) { 
       w.wait(); 
      } 
      // move to within synchronized block so sum 
      // updated value is required to be visible 
      System.out.println(w.sum); 
     } 
    } 

    @Override public synchronized void run() { 
     for (int i = 0; i < 1000000; i++) { 
      sum += i; 
     } 
     done = true; 
     // no notify required here, see nitpick at end 
    } 
} 

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

Когда поток ждет, он должен сделать это в цикле, где в тесте на цикле он проверяет некоторые условия. Другой поток должен установить эту переменную условия, чтобы первый поток мог ее проверить. Рекомендация, которую составляют the Oracle tutorial, составляет:

Примечание: всегда вызывайте ожидание внутри цикла, который проверяет состояние ожидания. Не предполагайте, что прерывание было для конкретного условия, которого вы ожидали, или что условие все еще верно.

Другие nitpicks:

  • Как ваш пример написано, виртуальная машина не требуется внести изменения в сумму переменной видна в основной поток. Если вы добавите метод синхронизированного экземпляра для доступа к переменной суммы или получите доступ к сумме в синхронизированном блоке, то основному потоку будет гарантировано увидеть обновленное значение суммы.

  • Глядя на ваше ведение журнала, нет ничего серьезного в прерванном исключении, это не значит, что все пошло не так. Прерывание прерывания возникает, когда вы вызываете прерывание по потоку, устанавливаете его флаг прерывания, и этот поток либо находится в ожидании, либо спящий, либо вводит метод ожидания или сна с установленным флагом. В моем примере игрушек в верхней части этого ответа я помещал исключение в предложение throws, потому что знаю, что этого не произойдет.

  • Когда поток завершается, он уведомляет об этом все, что ожидает что-либо, ожидающее этого объекта (опять-таки, это будет реализовано объединение). Стиль лучше использовать Runnable вместо Thread, отчасти из-за этого.

  • В этом конкретном примере было бы разумнее назвать Thread # join на суммирующем потоке, а не вызвать wait.

Вот пример переписан использовать объединение вместо:

public class J extends Thread { 
    private long sum; 

    synchronized long getSum() {return sum;} 

    public static void main(String[] args) throws InterruptedException { 
     J j = new J(); 
     j.start(); 
     j.join(); 
     System.out.println(j.getSum()); 
    } 

    @Override public synchronized void run() { 
     for (int i = 0; i < 1000000; i++) { 
      sum += i; 
     }   
    } 
} 

темы # Объединить ждать, блокировка на объекте потока. Когда суммарный поток завершается, он отправляет уведомление и устанавливает его флаг isAlive равным false. Между тем в методе соединения основной поток ожидает объекта суммирующего потока, он получает уведомление, проверяет флаг isAlive и понимает, что ему больше не нужно ждать, поэтому он может оставить метод соединения и распечатать результат.

Смежные вопросы