null

Синглтоноварение

О чем эта статья?

Данная статья посвящена паттерну проектирования Singleton и о способах его реализации. Мы разберем ключевые моменты при реализации, а также посмотрим в чем может быть проблема, если данные моменты не учесть.

Фаза 1. Осознание

В первую очередь, говоря о паттерне Singleton, стоит разделять паттерн от виски Lazy Singleton и Eager Singleton. Как понятно из названия, один из них создается лишь при необходимости, единожды в программе, другой же существует со старта. Давайте разберем Lazy Singleton, как более сложный и менее очевидный при реализации.

Фаза 2. Первая попытка

public class Singleton {

    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            return new Singleton();
        }
        return singleton;
    }
}

Самый простой метод реализации Lazy Singleton, у которого одна большая проблема - многопоточность. Из-за race condition по итогу у нас получится не Singleton, а doubleton, trippleton и т д. Давайте попробуем решить эту проблему.

Фаза 3. Осознание

Самое очевидное, что приходит сразу на ум, это поставить synchronized:

public class Singleton {

    private static Singleton singleton;

    private Singleton() {
    }

    public synchronized static Singleton getInstance() {
        if (singleton == null) {
            return new Singleton();
        }
        return singleton;
    }
}

Однако, это тоже плохое решение, так как если объект нужен большому количеству бизнес процессов в программе, то система постоянно будет виснуть на блокировках, поэтому просто synchronized нам не подойдет, нужно что-то лучше.

Фаза 4. Апгрейд

public class Singleton {

    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

Теперь же все выглядит куда лучше, мы ввели double check, если объект есть, мы его можем сразу же получить, если же его нет, тогда мы ставим блокировку и создаем его, в случае если он еще не создан другим потоком. Однако, тут тоже есть проблема и так сразу ее не увидеть. Новая IntelliJ уже умеет ее определять и в данном случае уже пытается нас спасти, выводя предупреждение: Make 'singleton' volatile. А все дело в оптимизациях Java, а если конкретнее, оптимизации Out-Of-Order Execution, суть которой, если вкратце, не копировать каждый раз на стек значение переменной, если этого не требуется, тем самым уменьшая количество операций с памятью. 

По итогу, благодаря этой оптимизации, все эти проверки не имеют смысла и мы откатились к фазе 2.

Фаза 5. Lazy Singleton

public class Singleton {

    private static volatile Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

И так, применив volatile, мы указываем, что переменная чувствительна и к ней не нужно применять оптимизации, благодаря чему мы смогли написать паттерн Singleton, который не создает баги.

Eager Singleton

Однако, помимо Lazy Singleton есть Eager Singleton, который куда проще и очевиднее, способов его реализации несколько.

Реализация JetBrains:

public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return singleton;
    }
}

Реализация через enum:

public enum Singleton {

    INSTANCE;

    public void doWork() {
        System.out.println("Singleton is whiskey");
    }
}

Также есть реализации через внутренний, вложенный класс.

 

Таким образом, мы рассмотрели различные варианты синглтоноварения, однако, тут все еще есть проблемы, ведь как мы знаем, Singleton является как паттерном, так и антипаттерном и подобные Singleton'ы, написанные своими руками в программе, являются антипаттернами, а причину этого мы возможно рассмотрим в следующих статьях.