Вопрос по concurrency, multithreading, java – Java-параллелизм на практике - пример 14.12

15
<code>// Not really how java.util.concurrent.Semaphore is implemented
@ThreadSafe
public class SemaphoreOnLock {
    private final Lock lock = new ReentrantLock();
    // CONDITION PREDICATE: permitsAvailable (permits > 0)
    private final Condition permitsAvailable = lock.newCondition();
    @GuardedBy("lock") private int permits;

    SemaphoreOnLock(int initialPermits) {
        lock.lock();
        try {
            permits = initialPermits;
        } finally {
            lock.unlock();
        }
    }

/* other code omitted.... */
</code>

У меня есть вопрос о примере выше, который извлечен изJava Concurrency in Practice Листинг 14.12. Подсчет семафоров, реализованных с использованием Lock.

Мне интересно, почему мы должны получить блокировку в конструкторе (как показано, вызывается lock.lock ()). Насколько я знаю, конструкторatomic (кроме ссылки, которой удалось избежать), поскольку никакой другой поток не может получить ссылку, следовательно, полу-построенный объект не виден другим потокам. Поэтому нам не нужен синхронизированный модификатор для конструкторов. Кроме того, нам не нужно беспокоиться оmemory visibility а также, пока объект благополучно опубликован.

So, why do we need to get the ReentrantLock object inside the constructor?

Я согласен, блокировка в конструкторе кажется ненужной. helloworld922
Я подозреваю, что это может быть для того, чтобы соответствовать@GuardedBy аннотация. trutheality
@ ben, @ helloworld922, @ trutheality: требуется блокировка. смотри мой ответ. Prince John Wesley

Ваш Ответ

3   ответа
12

Это не верно. Объект виден другим потокам во время создания, если он имеет какой-либо не окончательный / изменчивый поля. Поэтому другие потоки могут видеть значение по умолчанию дляpermits т.е.0 который может не соответствовать текущей теме.

одель памяти @Java предлагает специальную гарантию безопасность инициализации для неизменяемых объектов (объект только с конечными полями). Ссылка на объект, видимая для другого потока, не обязательно означает, что состояние этого объекта является видимым для потока-потребителя -JCP $3.5.2

Из листинга 3.15 Java-параллелизма на практике:

Хотя может показаться, что значения полей, заданные в конструкторе, являются первыми значениями, записанными в эти поля, и, следовательно, нет более старых значений, которые можно увидеть как устаревшие значения, Object constructor сначала записывает значения по умолчанию во все поля перед выполнением конструкторов подкласса. Поэтому можно увидетьдефол значение для поля как Черствый ценность

@ benben: видимость ссылки на объект и видимость состояния объекта - это разные вещи. и ссылка на объект, и состояние объекта должны быть видны другим потокам одновременно. Прочитайте Листинг JCP $ 3.14. В твоем случае field guarded by a lock используется Prince John Wesley
field guarded by a lock будет означать@GuardedBy("lock") SemaphoreOnLock sol = ...;. Блокировка не находится внутри конструктора класса SemaphoreOnLock. Это означает блокировку некоторых других классов с использованием объекта SemaphoreOnLock. ben ben
Большое спасибо за ответы, и я очень ценю, что ваш ответ очень подробный. однако во время построения потоки, отличные от конструирующего потока, не могут получить ссылку на объект. Поскольку другие потоки не могут получить ссылку на объект, они не могут прочитатьint стоимость объекта. поэтому другие потоки не могут прочитать устаревшее значение, так как они даже не могут прочитать значение. кроме того, если объект безопасно опубликован (т.е. статический инициализатор / конечное поле / изменчивое поле / поле, защищенное блокировкой). «Проблема устаревших значений» не возникнет. ben ben
@ benben: значениеpermits должно быть одинаковым для всех потоков. Чтобы сделать его согласованным, нужно либо пометить его как volatile (слабую семантику) (так как он имеет типint) или охраняйте его замком (сильная семантика).@GuardedBy аннотация говорит о том, что конкретное поле защищено блокировкой. Это более чистый способ сообщить программисту, что он / она всегда должен использовать заданную блокировку для доступа к полю. Здесь требование к видимости равноpermit должен равняться самому себе. Я имею в виду, это должно быть последовательным. Вам нужно ограничить память, чтобы значение разрешения было одинаковым для всех потоков. Prince John Wesley
да,permit должен быть защищен блокировкой методов получения / установки, чтобы обеспечить видимость памяти и взаимное исключение между потоками ПОСЛЕ конструирования, но не ВО ВРЕМЯ конструирования. Во время конструирования объекта только один поток (конструирующий поток) может достигнуть полу-построенного объекта (не допускайте выхода из ссылки), поэтому взаимное исключение не требуется. Более того, безопасная публикация гарантирует, что другие потоки смогут получить последнее значение ссылки на объект (и другие относительные поля), поэтому гарантируется видимость памяти. Потокобезопасность обеспечивается без блокировки. ben ben
0

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

В этом случае они могли просто установить поле на volatile. Сам замок защищает единственное присвоение, которое является атомарным, поэтому единственный полезный эффект - забор памяти. Tudor
На самом деле, нет. два задания не являются атомарными. Не финальные поля сначала инициализируются значением по умолчанию, а затем происходит фактическое присвоени Prince John Wesley
yes volatile можно использовать, так как int атомарный. но этот пример имеетfield guarded by a lockеханизм @ (ограничитель памяти), обеспечивающий согласованность видимости состояния объекта в потоках. Prince John Wesley
@ Prince John Wesley Ты действительно думаешь, что энергозависимости недостаточно для видимости потока? gstackoverflow
0

еты верны).

Экземпляры этого гипотетическогоSemaphoreOnLock класс предназначен для совместного использования. Так что нитьT1 полностью создает экземпляр и помещает его туда, где потокT2 может увидеть это и вызвать какой-то метод, который требует чтенияpermits поле. Несколько важных вещей, которые стоит отметить оpermits поле:

первом случае @it инициализируется значением по умолчанию0атем @it присваивается значение (которое может отличаться от значения по умолчанию0), по темеT1это неvolatileэто неfinal (что делает его похожим на «одноразовый выстрел»)

Поэтому, если мы хотимT2 чтобы прочитать значение, котороеT1 последний написал, нам нужно синхронизировать. Мы должны сделать это в конструкторе, как и в любом другом случае. (Тот факт, что это атомное присваивание или нет, не влияет на это Видимость выпуск). Стратегия Удерживающего построенныйSemaphoreOnLock для одного потока не работает для нас, потому что сама идея сделать это@Threadsafe так что мы можем безопасно делись этим.

То, что иллюстрирует этот пример, заключается в том, что «многопоточность» относится кстроительств объекта, при установке любого нестатического, не финального, энергонезависимого поля на значение, отличное от его значения по умолчанию.

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

Похожие вопросы