Дважды проверил блокировку Артикул
Я читал эту статью о "Двойной проверке блокировки", и из основной темы статьи мне было интересно, почему в какой-то момент статьи автор использует следующую идиому:
Листинг 7. Попытка решить проблему неупорядоченной записи
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 inst = new Singleton(); //4 } instance = inst; //5 } } } return instance; }
И мой вопрос: есть ли причина дважды синхронизировать некоторый код с одной и той же блокировкой? Есть ли для этого какая-то цель?
Спасибо заранее.
10 ответов
Точка блокировки дважды состояла в том, чтобы попытаться предотвратить неправильные записи. Модель памяти указывает, где могут происходить переупорядочения, частично в терминах блокировок. Блокировка гарантирует, что после "instance = inst;" не будет происходить никаких записей (в том числе внутри конструктора singleton). линия.
Однако, чтобы углубиться в тему, я бы порекомендовал статью Билла Пью. И тогда никогда не пытайтесь это сделать:)
В статье упоминается модель памяти Java (JMM) до 5.0. По этой модели выход синхронизированного блока принудительно записывает в основную память. Таким образом, представляется попытка убедиться, что объект Singleton выталкивается до обращения к нему. Однако, это не совсем работает, потому что запись в экземпляр может быть перемещена в блок - мотель-плотву.
Однако модель до 5.0 никогда не была правильно реализована. 1.4 должна следовать модели 5.0. Классы инициализируются лениво, так что вы можете просто написать
public static final Singleton instance = new Singleton();
Или лучше не используйте синглтоны, потому что они злые.
Джон Скит прав: прочитайте статью Билла Пью. Используемая Гансом идиома - это точная форма, которая не будет работать и не должна использоваться.
Это небезопасно:
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
Это также небезопасно:
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
Singleton inst = instance; //2
if (inst == null)
{
synchronized(Singleton.class) { //3
inst = new Singleton(); //4
}
instance = inst; //5
}
}
}
return instance;
}
Не делай ни одного из них, никогда.
Вместо этого синхронизируйте весь метод:
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
Если вы не извлекаете этот объект миллион раз в секунду, снижение производительности в реальном выражении будет незначительным.
Я покрываю кучу этого здесь:
http://tech.puredanger.com/2007/06/15/double-checked-locking/
Следуя рекомендации Jon Skeet:
Однако, чтобы углубиться в тему, я бы порекомендовал статью Билла Пью. И тогда никогда не пытайтесь это сделать:)
И вот ключ для второго блока синхронизации:
Этот код помещает конструкцию объекта Helper во внутренний синхронизированный блок. Интуитивно понятная идея здесь заключается в том, что в точке, где освобождается синхронизация, должен быть барьер памяти, и это должно предотвратить переупорядочение инициализации объекта Helper и присвоения помощнику поля.
Таким образом, в основном, с помощью внутреннего блока синхронизации, мы пытаемся "обмануть" JMM, создавая Экземпляр внутри блока синхронизации, чтобы заставить JMM выполнить это распределение до завершения блока синхронизации. Но проблема здесь в том, что JMM направляет нас вверх и перемещает назначение, которое находится перед блоком синхронизации внутри блока синхронизации, возвращая нашу проблему обратно к началу.
Это то, что я понял из этих статей, действительно интересно и еще раз спасибо за ответы.
Относительно этой идиомы есть очень рекомендуемая и уточняющая статья:
http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html?page=1
С другой стороны, я думаю, что dhighwayman.myopenid означает, что писатель поместил один синхронизированный блок, ссылающийся на тот же класс (synchronized(Singleton.class)), в другой синхронизированный блок, ссылающийся на тот же класс. Это может произойти, когда в этом блоке создается новый экземпляр (Singleton inst = instance;), и для обеспечения его поточной безопасности необходимо написать другой синхронизированный.
Иначе я не вижу никакого смысла.
Для Java 5 и выше есть вариант с двойной проверкой, который может быть лучше, чем синхронизация всего средства доступа. Это также упоминается в декларации блокировки с двойной проверкой:
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null)
helper = new Helper();
}
}
return helper;
}
}
Ключевым отличием здесь является использование volatile в объявлении переменной - иначе оно не работает и не работает в Java 1.4 или ниже, так или иначе.
Начиная с Java 5, вы можете сделать двойную проверку блокировки, объявив поле volatile.
См. http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html для полного объяснения.
Хорошо, но в статье сказано, что
Код в листинге 7 не работает из-за текущего определения модели памяти. Спецификация языка Java (JLS) требует, чтобы код в синхронизированном блоке не перемещался из синхронизированного блока. Однако это не говорит о том, что код, не находящийся в синхронизированном блоке, не может быть перемещен в синхронизированный блок.
И также кажется, что JVM делает следующий перевод для "псевдокода" в ASM:
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 //inst = new Singleton(); //4 instance = new Singleton(); } //instance = inst; //5 } } } return instance; }
До сих пор точка без записи после "instance=inst" не достигнута?
Я сейчас прочитаю статью, спасибо за ссылку.
См. Google Tech Talk по модели памяти Java для действительно хорошего знакомства с тонкостями JMM. Так как он здесь отсутствует, я также хотел бы указать на блог Джереми Мэнсона 'Java Concurrency' esp. пост о двойной проверке блокировки (любой, кто хоть что-то в мире Java, имеет статью об этом:).