Статьи

Двойная проверка блокировки на синглтон-классе в Java

Класс Singleton довольно распространен среди разработчиков Java, но он ставит много проблем для начинающих разработчиков. Одна из ключевых задач, стоящих перед ними, — как сохранить класс Singleton как Singleton? то есть как предотвратить множественные экземпляры Singleton по тем или иным причинам. Двойная проверка блокировки Singleton — это способ обеспечить создание только одного экземпляра класса Singleton в течение жизненного цикла приложения. Как следует из названия, при двойной проверке блокировки код проверяет существующий экземпляр класса Singleton дважды с блокировкой и без нее, чтобы гарантировать, что будет создано не более одного экземпляра singleton. Кстати, он был сломан до того, как Java исправила проблемы с моделями памяти в JDK 1.5. В этой статье мы увидим, как написать код для двойной проверки блокировки Singleton в Java , почему двойная проверка блокировки была нарушена до Java 5 и как это было исправлено. Кстати, это также важно с точки зрения собеседования, я слышал, что его попросили вручную кодировать блокировку синглтона с двойной проверкой для компаний финансового и сервисного секторов, и, поверьте мне, это сложно, пока у вас нет четкого понимания того, что ты делаешь. Вы также можете увидеть мой полный список вопросов по шаблонам проектирования Singleton, чтобы хорошо подготовиться.

Зачем вам нужна двойная проверка блокировки синглтон-класса?

Одним из распространенных сценариев, когда класс Singleton нарушает свои контракты, является многопоточность. Если вы попросите новичка написать код для шаблона проектирования Singleton , есть большая вероятность, что он придумает что-то вроде ниже:

1
2
3
4
5
6
7
8
private static Singleton _instance;
 
public static Singleton getInstance() {
        if (_instance == null) {
            _instance = new Singleton();
        }
        return _instance;
}

и когда вы укажете, что этот код создаст несколько экземпляров класса Singleton, если он будет вызван несколькими параллельными потоками, он, вероятно, синхронизирует весь этот метод getInstance (), как показано в нашем втором примере кода, метод getInstanceTS (). Хотя это потокобезопасный и решает проблему нескольких экземпляров, это не очень эффективно. Вы должны нести затраты на синхронизацию все время, когда вы вызываете этот метод, в то время как синхронизация необходима только в первом классе, когда создается экземпляр Singleton. Это приведет нас к двойной проверенной схеме блокировки , где блокируется только критическая часть кода. Программист назвал это двойной проверкой блокировки, потому что есть две проверки для _instance == null, одна без блокировки, а другая с блокировкой (внутри синхронизированной) блока. Вот как выглядит двойная проверка блокировки в Java:

01
02
03
04
05
06
07
08
09
10
public static Singleton getInstanceDC() {
        if (_instance == null) {                // Single Checked
            synchronized (Singleton.class) {
                if (_instance == null) {        // Double checked
                    _instance = new Singleton();
                }
            }
        }
        return _instance;
}

Синглтон Дизайн
На первый взгляд, этот метод выглядит идеально, так как вам нужно заплатить цену за синхронизированный блок только один раз, но он все еще не работает, пока вы не сделаете переменную _instance изменчивой . Без модификатора volatile для другого потока в Java можно увидеть половину инициализированного состояния переменной _instance, но с переменной volatile, гарантирующей связь «происходит до», вся запись будет происходить в volatile _instance перед любым чтением переменной _instance. Это было не так до Java 5, и поэтому дважды проверенная блокировка была сломана раньше. Теперь, с гарантией «до и после» , вы можете смело предполагать, что это сработает. Кстати, это не лучший способ создания поточно-ориентированного Singleton, вы можете использовать Enum как Singleton , который обеспечивает встроенную потоковую безопасность при создании экземпляра. Другой способ заключается в использовании статического держателя рисунка.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
 * A journey to write double checked locking of Singleton class in Java.
 */
 
class Singleton {
 
    private volatile static Singleton _instance;
 
    private Singleton() {
        // preventing Singleton object instantiation from outside
    }
 
    /*
     * 1st version: creates multiple instance if two thread access
     * this method simultaneously
     */
 
    public static Singleton getInstance() {
        if (_instance == null) {
            _instance = new Singleton();
        }
        return _instance;
    }
 
    /*
     * 2nd version : this definitely thread-safe and only
     * creates one instance of Singleton on concurrent environment
     * but unnecessarily expensive due to cost of synchronization
     * at every call.
     */
 
    public static synchronized Singleton getInstanceTS() {
        if (_instance == null) {
            _instance = new Singleton();
        }
        return _instance;
    }
 
    /*
     * 3rd version : An implementation of double checked locking of Singleton.
     * Intention is to minimize cost of synchronization and  improve performance,
     * by only locking critical section of code, the code which creates
 instance of Singleton class.
     * By the way this is still broken, if we don't make _instance volatile,
 as another thread can
     * see a half initialized instance of Singleton.
     */
 
    public static Singleton getInstanceDC() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null) {
                    _instance = new Singleton();
                }
            }
        }
        return _instance;
    }
}

Это все о двойной проверке блокировки класса Singleton в Java . Это один из спорных способов создания поточно-ориентированного Singleton в Java с более простыми альтернативами, доступными с точки зрения использования Enum в качестве класса Singleton. Я не предлагаю вам реализовывать ваш синглтон таким образом, так как есть много лучших способов реализовать шаблон синглтона в Java. Тем не менее, этот вопрос имеет историческое значение, а также учит, как параллелизм может внести незначительные ошибки. Как я уже говорил, это очень важно с точки зрения интервью. Потренируйтесь в написании двойной проверки блокировки класса Singleton вручную, прежде чем идти на любое интервью по Java. Это поможет вам понять ошибки кодирования, допущенные программистами Java. В связи с этим отметим, что в современный день разработки, основанной на тестировании, синглтон считается анти-паттерном из-за трудностей, с которыми он сталкивается, чтобы высмеивать его поведение, поэтому, если вы практикующий TDD, лучше избегать использования паттерна синглтона.