2015-10-31 2 views
3

Скажем, мы создаем поток, который запускает синхронизированный метод. Этот метод пытается выполнить take() из пустой очереди блокировки. Теперь позвольте отдельному потоку затем попытаться установить put() и элемент в очередь блокировки при синхронизации на одном и том же объекте.Тупик, вызванный методами блокировки

Это приводит в тупик:

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

Если два действия должны быть атомарными и выполняться на отдельных потоках, как это можно достичь, не вызывая тупика?

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

Пример:

import java.util.concurrent.*; 

public class DeadlockTest { 

    String input = "Nothing added yet!"; 
    LinkedBlockingQueue<String> buffer = new LinkedBlockingQueue<>(); 

    public synchronized String getFromBuffer() { 
     System.out.println("Trying to get input from buffer."); 
     try { 
      input = buffer.take(); 
     } catch (InterruptedException ex) {} 
     System.out.println("Got:" + input + "\n"); 
     return input; 
    } 

    public static void main(String[] args) throws InterruptedException { 
     DeadlockTest dl = new DeadlockTest(); 

     new Thread(() -> { 
      dl.getFromBuffer(); 
     }).start(); 

     // Give new thread time to run. 
     Thread.sleep(500); 

     synchronized (dl) { 
      String message = "Hello, world!"; 

      System.out.println("Adding: " + message); 
      dl.buffer.put(message); 
      System.out.println("Added!\n"); 

      System.out.println("Message: " + dl.input); 
     } 
    } 
} 
+0

Ну, да, так что возьмите/поп/что угодно, чтобы освободить замок, даже если очередь пуста. –

+0

Обычно вы используете дополнительный поток для выполнения блокировки, а затем читаете результаты с помощью какого-либо цикла событий. –

+0

@MartinJames Объект блокировки, который 'take()' и 'put()' приобретает и выпускает, не является тем же, который использует синхронизированный метод. У меня нет доступа к внутренней блокировке для блокирующей очереди.Если бы я это сделал, я мог бы использовать его в качестве аргумента для синхронизированного блока вместо того, чтобы синхронизировать этот метод. – nolanar

ответ

3

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

Звучит как плохой дизайн. Обычно бывает ошибкой вызывать любые методы блокировки из метода synchronized или оператора synchronized.

Если два действия должны быть атомарными и работать на отдельных потоках, как это можно достичь, не вызывая тупик?

Ну, есть две возможности:

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

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


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

То, что вы хотите (и то, что вы получите от любого из реализаций очереди блокировки в стандартной библиотеке Java) является то, что put() операция во втором потоке разбудит нить, которая ждет, чтобы take() что-то из очереди.

+0

Спасибо за отзыв Джеймс! Плохой дизайн, безусловно, проблема. Вот почему я спрашиваю, как я могу добиться подобного эффекта с другим дизайном. Я понимаю, что 'take()' действительно освобождает блокировку, когда она блокируется. Однако это не тот объект блокировки, который использует синхронизированный метод. Если бы я имел доступ к объекту блокировки, внутреннему к очереди блокировки, то я мог бы использовать его как аргумент для синхронизированного блока. Это, вероятно, тоже плохой дизайн! – nolanar

+0

Первичная функция, которую вы хотите, это механизм 'wait()/notify()'. Посмотрите на методы 'productionSomething()' и 'consumeSomething()' в ответе на другой вопрос. http://stackoverflow.com/questions/26590542/java-lang-illegalmonitorstateexception-object-not-locked-by-thread-before-wait/26593239#26593239 Вызов 'lock.wait()' разрешен только внутри ' synchronized (lock) '. Он временно открывает блокировку, он ждет уведомления другим потоком, а затем он снова блокирует блокировку перед возвратом. –

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