8

Я читал о некоторых деталях реализации ReentrantLock в «Java Параллелизм на практике», раздел 14.6.1, и что-то в аннотации заставляет меня путать:Как выполняется переписывание текущей переменной потока в ReentrantLock.Sync?

Поскольку защищенные методы государственной манипуляции имеют семантика памяти волатильного чтения или записи и ReentrantLock осторожно, чтобы читал поле владельца только после вызова getState и написал его только до вызова setState, ReentrantLock может копировать в семантику памяти состояния синхронизации и, таким образом, избегать дополнительной синхронизации. Раздел 16.1.4.

код, который он ссылается на:

protected boolean tryAcquire(int ignored) { 
    final Thread current = Thread.currentThread(); 
    int c = getState(); 
    if (c ==0) { 
     if (compareAndSetState(0, 1)) { 
      owner = current; 
      return true; 
     } 
    } else if (current == owner) { 
     setState(c+1); 
     return true; 
    } 
} 

И я считаю, что это упрощенный код nonfirTryAcquire в ReentrantLock.Sync.

final boolean nonfairTryAcquire(int acquires) { 
    final Thread current = Thread.currentThread(); 
    int c = getState(); 
    if (c == 0) { 
     if (compareAndSetState(0, acquires)) { 
      setExclusiveOwnerThread(current); 
      return true; 
     } 
    } 
    else if (current == getExclusiveOwnerThread()) { 
     int nextc = c + acquires; 
     if (nextc < 0) // overflow 
      throw new Error("Maximum lock count exceeded"); 
     setState(nextc); 
     return true; 
    } 
    return false; 
} 

Итак, озадачивает часть, как установка owner, которая является всего лишь обычная переменная экземпляра в AbstractOwnableSynchronizer, становится видимым else if (current == owner) в других потоках. Действительно, чтение owner происходит после вызова getState()state - это volatile квалифицированная переменная AQS), но после установки owner ничего нет (можно установить семантику синхронизации). Происходит гонка данных?

Ну, в свете власти этой книги и тщательно протестированного кода, две возможности прийти на ум:

  1. The полный барьер (будь то mfence или «lock'ed инструкцию) до настройка owner = current делает скрытую работу. Но из того, что я узнал из нескольких известных статей, полный барьер больше заботится о писаниях перед ним, а также о чтениях после него. Ну, если эта возможность верна, то некоторые предложения в «JCIP» могут быть неправильно указаны.

  2. Я заметил, что «географически» setState(c+1) действительно приходит после owner = current в фрагменте кода, хотя он находится в другой ветке if-else. Если, как говорят комментарии, это правда, означает ли это, что барьер, вставленный setSate(c+1), может наложить семантику синхронизации на owner = current в другую ветку?

Я новичок в этой области, а также несколько больших блоги мне очень помогают в понимании того, что никогда, лежащей в основе виртуальной машины Java (без заказа):

, а также всегда великолепный: http://g.oswego.edu/dl/jmm/cookbook.html

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

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

+0

Возможно, вас заинтересует «Ракурс синхронизации java.util.concurrent» Дуга Ли. http://gee.cs.oswego.edu/dl/papers/aqs.pdf – ewernli

+0

Это один из немногих за пределами меня сейчас, я думаю, но я попробую. Спасибо, @ewernli. И благодаря rolfl за то, что вы меня выпустили, я никогда не должен забывать большую (большую?) Картину. – DarKeViLzAc

ответ

2

Вы подозреваете, что может быть гонка между owner = current; (после CAS) и if (current == owner) (после считывания состояния и проверки, если оно> 0).

Принимая этот фрагмент кода в изоляции, я думаю, что ваши рассуждения верны. Тем не менее, вы должны рассмотреть tryRelease, а также:

123:   protected final boolean tryRelease(int releases) { 
124:    int c = getState() - releases; 
125:    if (Thread.currentThread() != getExclusiveOwnerThread()) 
126:     throw new IllegalMonitorStateException(); 
127:    boolean free = false; 
128:    if (c == 0) { 
129:     free = true; 
130:     setExclusiveOwnerThread(null); 
131:    } 
132:    setState(c); 
133:    return free; 
134:   } 

Здесь владелец устанавливается на null перед государством устанавливается в 0. Чтобы сначала получить блокировку, государство должно быть 0, и поэтому владелец null ,

Следовательно,

  • Если поток достигает if (current == owner) с c=1,
    • может быть владеющей нитью, и в этом случае владелец правильно и состояние увеличивается.
    • это может быть другой поток, который может видеть или не новый владелец.
      • Если он видит это, все в порядке.
      • Если нет, он увидит null, что тоже хорошо.
  • Если поток достигает if (current == owner) с c>1,
    • может быть владеющей нитью, и в этом случае владелец правильно и состояние увеличивается.
    • это может быть другой поток, но владелец будет верным.

Я aggree, что сноска «читать поле владельца только после вызова GetState и писать только перед вызовом SetState» в JCIP вводит в заблуждение. Он пишет owner перед вызовом setState в tryRelease, но не tryAcquire.

+0

Точно! Я приму свой ответ. Но с точки зрения логики, я также почувствовал, что rolfl может иметь некоторые моменты. Без volatile или piggybakc «владелец» просто остается в рабочей памяти потока (кеш). Когда определенный поток входит в 'tryAcquire' и обнаруживает, что' c! = 0' сам не является «фактическим владельцем», даже если «владелец» может быть устаревшим или нулевым, нет никаких шансов, что поток ошибочно рассмотрит вопрос о том, быть владельцем, и заключить договор (протокол). Ну, конечно, если вы сначала попробуетеAssquire (3) ', а затем' tryRelease (1) ',' owner' определенно будет точным. – DarKeViLzAc

+0

@DarKeViLzAc Что важно, так это то, что когда поток освобождает блокировку (c = 0), он устанавливает для владельца значение null. Если это не так, может произойти следующее: Изначально c = 1 и owner = T1. T1 освобождает блокировку и устанавливает c = 0, но не сбрасывает владельца. T2 устанавливает c = 1 и owner = T2. T1 пытается снова получить блокировку, он видит c = 1 и owner = T1 (поскольку изменение от T1 до T2 пока не отражено). Он неправильно увеличивает счетчик, c = 2. – ewernli

+0

Я согласен с вами, хотя я по-прежнему не могу не задаться другим вопросом: если «владелец» не имеет преимущества в состоянии волатильности (без контрейлерных соединений), но по-прежнему должен иметь значение null, блокировка будет работать, как ожидалось. Ну, просто дикая мысль, пожалуйста, не считайте это слишком серьезным. :) – DarKeViLzAc

0

Это хорошо объясняется в this blog post. Итог заключается в том, что, когда поток чтения считывает изменчивое поле, все поля, обновленные потоком записи, которые были изменены перед записью в поле volatile, также будут видны в потоке чтения. Класс блокировки организует поле обращается для того, чтобы только состояние поля должно быть неустойчивым, а поле владельца еще безопасно propogated при необходимости

+0

Именно это меня смущает: поле владельца обновляется ** после операции CAS в нестабильное поле **. Здесь не работает так называемая контрейлерная обработка. Спасибо за ссылку, хотя, еще один в сборник. – DarKeViLzAc

+1

Да, но единственный раз, когда содержание поля владельца значимо, - это ** после **, мы знаем, что c! = 0, и для этого мы также знаем, что ** мы ** являемся владением потоком, и поэтому мы будем не имеет проблемы с состоянием переменной в модели памяти (если мы не являемся ничем не владеющим, тогда нет пути для 'owner == Thread.currentThread()' – rolfl

+0

Я получаю это. Ваше объяснение имеет смысл. Я слишком сфокусировался на предмете забора, который просто сузил мое видение и пренебрег логической причиной. Но, говоря о том, что я спросил выше, вы можете помочь мне прийти к заключению. Более конкретно, текст, который я упоминаю (выделен жирным шрифтом) – DarKeViLzAc