Статьи

Пример шаблона синглтона

Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .

В этом курсе вы изучите огромное количество шаблонов проектирования и увидите, как они реализуются и используются в Java. Вы поймете причины, почему шаблоны так важны, и узнаете, когда и как применять каждый из них. Проверьте это здесь !

1. Синглтон

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

Вам может потребоваться только один объект класса, например, когда вы создаете контекст приложения или управляемый потоком пул, параметры реестра, драйвер для подключения к консоли ввода или вывода и т. Д. Более одного объекта этот тип явно вызовет несоответствие вашей программе.

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

фигура 1

фигура 1

В этом уроке мы попробуем разные способы создания только одного объекта класса, а также увидим, как один способ лучше другого.

2. Как создать класс с использованием шаблона Singleton

Существует много способов создать класс такого типа, но все же мы увидим, что один способ лучше другого.

Давайте начнем с простого способа.

Что если мы предоставим глобальную переменную, которая делает объект доступным? Например:

1
2
3
4
5
6
package com.javacodegeeks.patterns.singletonpattern;
 
public class SingletonEager {
    public static SingletonEager sc = new SingletonEager();
         
}

Как мы знаем, есть только одна копия статических переменных класса, мы можем применить это. Поскольку клиентский код использует эту статическую переменную sc это нормально. Но если клиент использует новый оператор, будет новый экземпляр этого класса.

Чтобы остановить создание экземпляра класса вне класса, давайте сделаем конструктор класса закрытым.

1
2
3
4
5
6
7
package com.javacodegeeks.patterns.singletonpattern;
 
public class SingletonEager {
    public static SingletonEager sc = new SingletonEager();
    private SingletonEager(){}
     
}

Это сработает? Я думаю да. Сохраняя конструктор закрытым, никакой другой класс не может создать экземпляр этого класса. Единственный способ получить объект этого класса — использовать статическую переменную sc которая обеспечивает наличие только одного объекта.

Но, как мы знаем, предоставление прямого доступа ученику не является хорошей идеей. Мы предоставим метод, через который переменная sc получит доступ, а не напрямую.

1
2
3
4
5
6
7
8
9
package com.javacodegeeks.patterns.singletonpattern;
 
public class SingletonEager {
    private static SingletonEager sc = new SingletonEager();
    private SingletonEager(){}
    public static SingletonEager getInstance(){
        return sc;
    }
}

Итак, это наш одноэлементный класс, который обеспечивает создание только одного объекта класса, и даже если есть несколько запросов, будет возвращен только один и тот же экземпляр объекта.

Единственная проблема этого подхода заключается в том, что объект будет создан, как только класс будет загружен в JVM. Если объект никогда не запрашивается, в памяти будет бесполезный объект.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
package com.javacodegeeks.patterns.singletonpattern;
 
public class SingletonLazy {
     
    private static SingletonLazy sc = null;
    private SingletonLazy(){}
    public static SingletonLazy getInstance(){
        if(sc==null){
            sc = new SingletonLazy();
        }
        return sc;
    }
}

В методе getInstance() мы проверяем, является ли статическая переменная sc нулевым, затем создаем экземпляр объекта и возвращаем его. Таким образом, при первом вызове, когда sc будет нулевым, объект будет создан, и при следующих последовательных вызовах он вернет тот же объект.

Это, безусловно, выглядит хорошо, не так ли? Но этот код потерпит неудачу в многопоточной среде . Представьте, что два потока одновременно обращаются к классу, поток t1 первым вызывает метод getInstance() , он проверяет, является ли статическая переменная sc нулевым, а затем прерывается по какой-то причине. Другой поток t2 вызывает метод getInstance() успешно проходит проверку if и создает объект. Затем поток t1 просыпается и также создает объект. В это время было бы два объекта этого класса.

Чтобы избежать этого, мы будем использовать ключевое слово synchronized для метода getInstance() . Таким образом мы заставляем каждый поток ждать своей очереди, прежде чем он сможет войти в метод. Таким образом, никакие два потока не будут вводить метод одновременно. Synchronized имеет свою цену, это снизит производительность, но если вызов метода getInstance() не вызывает значительных накладных расходов для вашего приложения, забудьте об этом. Другим обходным решением является переход к активному подходу к реализации, как показано в предыдущем примере.

01
02
03
04
05
06
07
08
09
10
11
12
13
package com.javacodegeeks.patterns.singletonpattern;
 
public class SingletonLazyMultithreaded {
 
    private static SingletonLazyMultithreaded sc = null;
    private SingletonLazyMultithreaded(){}
    public static synchronized SingletonLazyMultithreaded getInstance(){
        if(sc==null){
            sc = new SingletonLazyMultithreaded();
        }
        return sc;
    }
}

Но если вы хотите использовать синхронизацию, есть другой метод, известный как «двойная проверка блокировки», чтобы уменьшить использование синхронизации. С двойной проверкой блокировки мы сначала проверяем, создан ли экземпляр, а если нет, то мы синхронизируемся. Таким образом, мы синхронизируемся только в первый раз.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package com.javacodegeeks.patterns.singletonpattern;
 
public class SingletonLazyDoubleCheck {
 
    private volatile static SingletonLazyDoubleCheck sc = null;
    private SingletonLazyDoubleCheck(){}
    public static SingletonLazyDoubleCheck getInstance(){
        if(sc==null){
            synchronized(SingletonLazyDoubleCheck.class){
                if(sc==null){
                    sc = new SingletonLazyDoubleCheck();
                }  
            }
        }
        return sc;
    }
}

Помимо этого, есть несколько других способов сломать шаблон синглтона.

  1. Если класс является Сериализуемым.
  2. Если это Клонируемый.
  3. Это может быть сломано отражением.
  4. А также, если класс загружается несколькими загрузчиками классов.

В следующем примере показано, как вы можете защитить свой класс от создания экземпляра более одного раза.

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
package com.javacodegeeks.patterns.singletonpattern;
 
import java.io.ObjectStreamException;
import java.io.Serializable;
 
public class Singleton implements Serializable{
 
    private static final long serialVersionUID = -1093810940935189395L;
    private static Singleton sc = new Singleton();
    private Singleton(){
        if(sc!=null){
            throw new IllegalStateException("Already created.");
        }
    }
    public static Singleton getInstance(){
        return sc;
    }
     
    private Object readResolve() throws ObjectStreamException{
        return sc;
    }
     
    private Object writeReplace() throws ObjectStreamException{
        return sc;
    }
     
    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Singleton, cannot be clonned");
    }
     
    private static Class getClass(String classname) throws ClassNotFoundException {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        if(classLoader == null)
            classLoader = Singleton.class.getClassLoader();
        return (classLoader.loadClass(classname));
    }
     
}
  1. readResolve() и writeReplace() в вашем синглтон-классе и возвращайте через них один и тот же объект.
  2. Вам также следует реализовать метод clone() и выдать исключение, чтобы синглтон не мог быть клонирован.
  3. «Условие if» внутри конструктора может предотвратить создание экземпляра синглтона более одного раза с использованием отражения.
  4. Чтобы предотвратить создание экземпляров синглтона из разных загрузчиков классов, вы можете реализовать метод getClass() . Приведенный выше метод getClass() связывает загрузчик классов с текущим потоком; если этот загрузчик классов равен нулю, метод использует тот же загрузчик классов, который загрузил класс синглтона.

Хотя мы можем использовать все эти методы, существует один простой и более простой способ создания одноэлементного класса. Начиная с JDK 1.5, вы можете создать одноэлементный класс, используя перечисления. Константы Enum неявно являются статическими и конечными, и вы не можете изменять их значения после создания.

1
2
3
4
5
6
7
8
package com.javacodegeeks.patterns.singletonpattern;
 
public class SingletoneEnum {
 
    public enum SingleEnum{
        SINGLETON_ENUM;
    }
}

Вы получите ошибку времени компиляции при попытке явно создать экземпляр объекта Enum. Поскольку Enum загружается статически, он поточно-ориентирован. Метод клонирования в Enum является окончательным, что гарантирует, что константы enum никогда не будут клонированы. Enum по своей природе сериализуем, механизм сериализации гарантирует, что повторяющиеся экземпляры никогда не создаются в результате десериализации. Инстанцирование с использованием рефлексии также запрещено. Эти вещи гарантируют, что ни один экземпляр перечисления не существует вне того, что определено константами перечисления.

3. Когда использовать синглтон

  1. Должен быть ровно один экземпляр класса, и он должен быть доступен клиентам из известной точки доступа.
  2. Когда единственный экземпляр должен быть расширяемым за счет подклассов, и клиенты должны иметь возможность использовать расширенный экземпляр без изменения своего кода.

4. Загрузите исходный код

Это был урок по шаблону Singleton. Вы можете скачать исходный код здесь: SingletonPattern-Project