Двойная проверенная блокировка
Я читал эту статью о "двойной проверке блокировки" и из основной темы статьи мне было интересно, почему на некоторых в статье автор использует следующий Идиом:
Листинг 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;
}
И мой вопрос:
Есть ли причина для синхронизации дважды кода с той же блокировкой?
Есть ли в этом цель?
Большое спасибо заранее.
Ответы
Ответ 1
Точка блокировки дважды заключалась в попытке предотвратить запись вне порядка. Модель памяти указывает, где могут возникать переупорядочивания, частично в терминах блокировок. Блокировка гарантирует, что никакие записи (включая любые внутри конструктора singleton) не появятся после "instance = inst;" линия.
Однако, чтобы углубиться в предмет, я бы рекомендовал статью Билла Пьюга. И тогда никогда не пытайтесь это сделать:)
Ответ 2
В статье упоминается модель памяти Java до 5.0 (JMM). Под этой моделью, оставляя синхронизированный блок, принудительно выписывается в основную память. Похоже, что это попытка убедиться, что объект Singleton вытолкнут перед ссылкой на него. Однако это не совсем так, потому что запись в экземпляр может быть перенесена в блок - мотель roach.
Однако модель pre-5.0 никогда не была правильно реализована. 1.4 следует следовать модели 5.0. Классы инициализируются лениво, поэтому вы можете просто написать
public static final Singleton instance = new Singleton();
Или лучше, не используйте синглтоны, потому что они злы.
Ответ 3
Джон Скит прав: прочитайте статью Билла Пьюга. Идиома, которую использует Ганс, - это точная форма, которая не будет работать и не должна использоваться.
Это небезопасно:
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;
}
Если вы не возвращаете этот объект за миллион раз в секунду, производительность в реальном выражении незначительна.
Ответ 4
Я покрываю кучу этого здесь:
http://tech.puredanger.com/2007/06/15/double-checked-locking/
Ответ 5
Следуя John Skeet Рекомендация:
Однако, чтобы углубиться в тему Я бы рекомендовал статью Билла Пью. А также то никогда не пытайтесь это сделать:)
И вот ключ для второго блока синхронизации:
Этот код ставит Вспомогательный объект внутри внутреннего синхронизированный блок. Интуитивная идея вот что должно быть память барьер в точке, где синхронизация освобождается, и следует предотвратить переупорядочение инициализация объекта Helper и присвоение поля помощник.
Итак, в основном с блоком внутренней синхронизации мы пытаемся "обмануть" JMM, создав экземпляр внутри блока синхронизации, чтобы заставить JMM выполнить это распределение до завершения блока синхронизации. Но проблема здесь в том, что JMM направляет нас и перемещает привязку, которая находится перед блоком синхронизации внутри блока синхронизации, переместив нашу проблему обратно в начало.
Это то, что я понял из этих статей, действительно интересно и еще раз спасибо за ответы.
Ответ 6
Хорошо, но в статье сказано, что
Код в листинге 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" не выполняется?
Теперь я прочитаю статью, спасибо за ссылку.
Ответ 7
Начиная с Java 5, вы можете выполнить двойную проверку блокировки, объявив поле volatile.
Подробнее см. http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html.
Ответ 8
Относительно этой идиомы есть очень целесообразная и уточняющая статья:
http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html?page=1
С другой стороны, я думаю, что, по мнению dhighwayman.myopenid, автор поставил один синхронизированный блок, относящийся к одному и тому же классу (synchronized (Singleton.class)) в другом синхронизированном блоке, относящемся к тому же классу. Это может произойти, когда в этом блоке создается новый экземпляр (Singleton inst = instance;) и гарантируется, что он должен быть потокобезопасным, необходимо записать еще одну синхронизированную.
В противном случае я не вижу смысла.
Ответ 9
См. раздел Google Tech Talk в Java Memory Model для действительно приятного ознакомления с точками JMM. Поскольку он отсутствует здесь, я также хотел бы указать на блог Джереми Мэнсона 'Java Concurrency' esp. сообщение Двойная проверка блокировки (у любого, кто есть что-то в мире Java, есть статья об этом:).
Ответ 10
Для Java 5 и выше на самом деле существует вариант с двойным контролем, который может быть лучше, чем синхронизация всего аксессора. Это также упоминается в Double-Checked Locking Declaration:
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 или меньше.