понедельник, 13 июня 2011 г.

Что не так с вашим Singleton'ом?

Есть классическая реализация паттерна Singleton в Java для многопоточного использования. Так называемый double-checked locking.

  1.  
  2. public class Singleton {
  3.     private static Singleton instance;
  4.  
  5.     private Singleton {
  6.     }
  7.  
  8.     public static Singleton getInstance() {
  9.        if(instance == null) {
  10.             synchronized(this) {
  11.                if(instance == null) {
  12.                   instance = new Singleton();
  13.                }
  14.             }
  15.        }
  16.        return instance;
  17.     }
  18. }
  19.  

По логике все прекрасно, и создание объекта on-demand (ленивая инициализация), и не блокирующие чтение. Но, к сожалению, в некоторых случаях данный Singleton будет вести себя плохо. В чем же причина? Все дело в нюансах работы JVM с памятью, в многопоточной среде. Прежде чем описать проблему и приступить к решению, нужно вспомнить основные идиомы синхронизации при работе с памятью.
 — Атомарность. Целостность доступа (чтения и записи) к переменной в памяти. Гарантия что при запросе переменной будет возвращено ее значение либо до изменения одним из потоков, либо после. Но никак не промежуточное состояние (набор битов старого значения переписанное частично другим потоком).
 — Видимость. Видимость переменных измененных одним потоком в контексте другого.
 — Упорядоченность. Последовательно выполнение команд скомпилированного кода.

Если блок кода или метод синхронизирован, то все изменения, сделанные в контексте одного потока, атомарны, видимы для остальных потоков (использующих туже блокировку) и выполняются последовательно в соответствие с порядком кода в программе. Если синхронизации нет — все вышесказанное не гарантировано. Где же теряется синхронизация в нашем примере? Самое очевидное это отсутствие упорядоченности в строке instance = new Singleton ();. Когда компилятор видит конструктор, который не выбрасывает исключений и не выполняется синхронизированно, он может свободно поменять порядок инициализации объекта и его запись в перменную instance. Даже если порядок записи не меняется компилятором, он может быть изменен процессором в мультипроцессорной среде или самой системой памяти.
Самое важное в данной ситуации не придумывать велосипед, т.к. решая одну проблему синхронизации можно напороться на другую, которая проявит себя в специфичной ситуации совершенно не к месту. Многие гуру бились над этой проблемой и выработали приемлемые решения. Приводить обзор всех вариантов я не буду, укажу лишь только то, что использую сам.

1. Класс-холдер.

  1.  
  2. public class Singleton {
  3.  
  4.     private Singleton {
  5.     }
  6.    
  7.     private static class SingletonHolder {
  8.        private final static Singleton instance = new Singleton();
  9.     }
  10.  
  11.     public static Singleton getInstance() {
  12.        return SingletonHolder.instance;
  13.     }
  14. }
  15.  

Красивый способ, без явной синхронизации, инициализация on-demand. Но конструктор не должен выбрасывать эксепшенов, если есть уверенность что исключений не будет — используем этот вариант.

2. Доработанный double-checked locking пример.

  1.  
  2. public class Singleton {
  3.     private volatile static Singleton instance;
  4.  
  5.     private Singleton {
  6.     }
  7.  
  8.     public static Singleton getInstance() {
  9.        if(instance == null) {
  10.             synchronized(this) {
  11.                if(instance == null) {
  12.                   instance = new Singleton();
  13.                }
  14.             }
  15.        }
  16.        return instance;
  17.     }
  18. }
  19.  

Модификатор volatile гарантирует синхронизированное обращение к перменной instance. Но работает только на jdk 5 и выше.

Статья изложена, осознана, переведена с использованием источников:

Реализация Singleton в JAVA

Synchronization and the Java Memory Model

The «Double-Checked Locking is Broken» Declaration
______________________
Текст подготовлен в Редакторе Блогов от © SoftCoder.ru

Комментариев нет:

Отправить комментарий