2016-05-19 14 views
2

Мне интересно, есть ли простой способ сделать блокировку , которая будет реагировать на изменение ссылок. У меня есть код, который выглядит примерно так:Повторная проверка измененной ссылки для «синхронизированной» блокировки?

private void fus(){ 
    synchronized(someRef){ 
     someRef.roh(); 
    } 
} 
... 
private void dah(){ 
    someRef = someOtherRef; 
} 

То, что я хотел бы случиться:

  1. Поток А входит fus, и получает блокировку на someref, как он называет roh(). Предположим, что roh никогда не заканчивается.
  2. Резьба B вводит fus, начинает ждать, пока некоторый Ref` будет свободным и останется там (на данный момент).
  3. Резьба C вводится dah и изменяет someRef.
  4. В настоящее время разрешено вводить поток В, в который введено значение someRef. Объект Thread имеет блокировку.

Что происходит на самом деле:

  1. Поток А входит fus, и получает блокировку на someref, как он называет roh(). Предположим, что roh никогда не заканчивается.
  2. Резьба B вводит fus, находит замок и ожидает его освобождения (навсегда).
  3. Резьба C вводится dah и изменяет someRef.
  4. Thread B продолжает ждать, так как это уже не глядя на someref, он смотрит на замке, принадлежащего А.

Есть ли способ, чтобы установить это такую, что тему B будет либо повторно проверить блокировка для изменения ссылок или «отскакивание» в другой код? (что-то вроде sychronizedOrElse?)

+1

Похоже, что вы синхронизируете не тот объект. – user2357112

+0

Это любопытство или вы ищете решение? – UDKOX

+0

@UDOX - немного каждого.В моем коде достаточно краев, что это не вызывает большой озабоченности (если метод, эквивалентный «roh», не может завершиться, это, вероятно, проблема сама по себе, что мои блокировки испортились, это не настоящий преступник), но было бы хорошо знать, и, похоже, было бы полезно избегать взаимоблокировок. –

ответ

3

Там, конечно, есть способ, но не с synchronized. Рассуждение: в момент времени, когда второй поток входит в fus(), первый поток содержит внутреннюю блокировку объекта, на который ссылается someRef. Важно: второй поток по-прежнему будет видеть ссылку someRef на этом самом объекте и попытается получить эту блокировку. Позже, когда третий поток изменит ссылку someRef, он должен будет как-то уведомить 2-й поток об этом событии. Это невозможно с synchronized.

Насколько мне известно, встроенная языковая функция, такая как , не подходит для такой синхронизации.

Несколько иной подход заключается в том, чтобы либо управлять Lock в вашем классе, либо давать someRef атрибут типа Lock. Вместо работы с lock() вы можете использовать tryLock() или tryLock(long timeout, TimeUnit unit).Это схема того, как я бы это реализовать (при условии, что someRef имеет атрибут Lock):

volatile SomeRef someRef = ... // important: make this volatile to deny caching 
... 
private void fus(){ 
    while (true) { 
     SomeRef someRef = this.someRef; 
     Lock lock = someRef.lock; 
     boolean unlockNecessary = false; 
     try { 
      if (lock.tryLock(10, TimeUnit.MILLISECONDS)) { // I have chonse this arbritrarily 
       unlockNecessary = true; 
       someRef.roh(); 
       return; // Job is done -> return. Remember: finally will still be executed. 
         // Alternatively, break; could be used to exit the loop. 
      } 
     } catch (InterruptException e) { 
      e.printStackTrace(); 
     } finally { 
      if (unlockNecessary) { 
       lock.unlock(); 
      } 
     } 
    } 
} 
... 
private void dah(){ 
    someRef = someOtherRef; 
} 

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

2

Что происходит на самом деле ... Тема B продолжает ждать, как это уже не глядя на someref, он не смотрит на замке, принадлежащего А.

Это верно. Вы не можете писать код для синхронизации на переменной . Вы можете писать код только для синхронизации на некотором объекте.

Thread B нашел объект, на котором можно синхронизировать, посмотрев на переменную someref, но она всегда смотрит на эту переменную один раз, чтобы найти объект. Объект - это то, что он блокирует, и пока нить A не освободит замок на этом объекте, поток B застрянет.

1

Я хотел бы добавить дополнительную информацию по отличным ответам от @Turing85 и @james large.

Я согласен с тем, что Thread B продолжает ждать.

Лучше избегать synchronization для этого типа программ, используя лучший API без блокировки.

Atomic переменные имеют функции, которые минимизируют синхронизацию и помогают избежать ошибок согласованности памяти.

Из кода, который вы опубликовали, AtomicReference представляется правильным решением проблемы.

Посмотрите страницу документации по упаковке Atomic.

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

boolean compareAndSet(expectedValue, updateValue); 

Еще один хороший пост в SE связанного в тему.

When to use AtomicReference in Java?

Пример кода:

String initialReference = "value 1"; 

AtomicReference<String> someRef = 
    new AtomicReference<String>(initialReference); 

String newReference = "value 2"; 
boolean exchanged = someRef.compareAndSet(initialReference, newReference); 
System.out.println("exchanged: " + exchanged); 

Обратитесь к этому jenkov учебник для лучшего понимания.

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