Могут ли блокировки собирать мусор во время блокировки?

Может ли блокировка (java.util.concurrent.locks.Lock) собирать мусор во время блокировки? Предположим чисто теоретический пример:

WeakReference r;

public void foo(){
       Lock lock = new ReentrantLock();
       r = new WeakReference(lock);   
       lock.lock();
}

Может ли lock быть собранным мусором после выполнения foo()? Другими словами, lock.lock() создает любые сильные ссылки обратно на блокировку? Откуда вы знаете?

Ответы

Ответ 1

Заблокированный Lock может быть собран в мусор, когда он больше не доступен. (Определение "достижимое" в JLS: "Досягаемым объектом является любой объект, к которому можно получить доступ в любом потенциальном продолжающемся вычислении из любого живого потока". - JLS 12.16.1)

Однако заблокированный Lock, который ожидает какой-то поток, должен выполнять один из методов экземпляра Lock lock/tryLock. Чтобы это произошло, поток должен иметь ссылку на блокировку; то есть тот, который в настоящее время осуществляет метод блокировки. Следовательно, заблокированная блокировка, которую пытается использовать какой-либо поток, достижима и не может быть собрана мусором.

Иными словами, блокирует ли lock.lock() любые сильные ссылки на блокировку?

Нет. В вашем примере сильная ссылка существует в виде переменной Lock. Но предположим, что мы изменили ваш пример, чтобы избавиться от Lock; например.

public void do_lock(WeakReference<ReentrantLock> r) 
   r.get().lock();
}

Когда вы вызываете get(), он вернет ссылку на объект ReentrantLock, который будет храниться во временной переменной или регистре, что сделает его доступным для достижения цели. Он будет по-прежнему доступен для достижения цели, пока выполняется вызов lock(). Когда вызов lock() возвращается, объект ReentrantLock может стать недоступным (снова).

Откуда вы знаете?

Откуда я знаю? Комбинация:

  • знание определения языка Java для определения достижимости и других вещей,
  • опыт внедрения JVM,
  • хорошая старомодная логика и...
  • Я подтвердил это, прочитав исходный код OpenJDK (хотя это вообще ничего не доказывает о JVM вообще.)

Нет необходимости реализовывать Lock с использованием глобальных очередей, и, следовательно, нет причин иметь скрытую ссылку на объект Lock, который помешал бы ему стать недоступным. Кроме того, Lock, который не мог быть собран с мусором, когда он был заблокирован, будет утечкой хранилища и серьезным недостатком реализации, и я не могу представить, чтобы Дуг Ли и др. Допустили эту ошибку!

Ответ 2

Оказывается, что, хотя мы часто концептуально считаем, что потоки "получают" и "собственные" блокировки, на самом деле это не так с точки зрения реализации. Блокировки содержат ссылки на собственные и ожидающие потоки, в то время как потоки не имеют ссылок на блокировки и не знают о том, какие блокировки они "принадлежат".

Реализация ReentrantLock также довольно проста: нет статических коллекций замков, и нет потоков поддержки фона, которые отслеживают блокировки.

Ни создание, ни блокировка блокировки не создают никаких "скрытых" новых сильных ссылок в любом месте, поэтому в приведенном выше примере lock может быть собрано мусор после завершения foo().

Это можно проверить, просмотрев исходный код:

http://fuseyism.com/classpath/doc/java/util/concurrent/locks/ReentrantLock-source.html

http://fuseyism.com/classpath/doc/java/util/concurrent/locks/AbstractQueuedSynchronizer-source.html

http://fuseyism.com/classpath/doc/java/util/concurrent/locks/AbstractOwnableSynchronizer-source.html

Ответ 3

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

(java.util.concurrent.locks.) Lock (s) - абсолютно нормальные объекты, отличные от java.util.ArrayList с точки зрения сбора мусора. Я написал блокировки с семантикой LIFO (или, в частности, AbstractQueueSynchronized, которая не является FIFO), что полезно для минимизации промахов в кеше, поскольку самые горячие потоки становятся работать еще больше. Точка - это то, что абсолютно возможно написать собственный код блокировки/синхронизации/атома.

Ответ 4

Предполагая, что вы имеете в виду "после выполнения foo()", ответ "да" - это действительно точка WeakReferences.

Вы бы это знали, потому что, когда вы пытались преобразовать WeakReference r обратно в регулярную (или сильную) ссылку, вы получили бы возвращенный null:

if (r.get() == null) {
    // the lock was garbage collected
}

Ответ 5

Блокировка не похожа ни на какой другой объект. Это зависит от того, ссылается ли какой-либо внутренний механизм Java на блокировку. Но я не вижу причин, почему Java должна содержать ссылку на Lock.

Ответ 6

Это может быть сбор мусора, когда он заблокирован. Сильная ссылка не создается путем блокировки. Как написано, конечно, вам нужно установить блокировку в null и запустить gc, чтобы увидеть, что ссылка go null.