Синглтон класс в Java
Просто я думал о других способах написания синглтон-класса. Так считается ли этот класс одиночным?
public class MyClass{
static Myclass myclass;
static { myclass = new MyClass();}
private MyClass(){}
public static MyClass getInstance()
{
return myclass;
}
}
так как статический блок запускается только один раз.
10 ответов
Нет. Вы не объявили myClass
private static final
ни getInstance()
является static
, Код также не компилируется.
Вот идиома Синглтона:
public class MyClass {
private static final MyClass myClass = new MyClass();
private MyClass() {}
public static MyClass getInstance() {
return myClass;
}
}
Так должно быть private
так, чтобы никто другой не мог получить к нему доступ напрямую. Так должно быть static
так что есть только один из них. Так должно быть final
так что это не может быть переназначено. Вам также нужно создать его экземпляр непосредственно во время объявления, чтобы вам не нужно было слишком сильно беспокоиться о многопоточности.
Если загрузка стоит дорого, и поэтому вы предпочитаете ленивую загрузку Singleton, рассмотрите идиому держателя Singleton, которая выполняет инициализацию по требованию, а не во время загрузки класса:
public class MyClass {
private MyClass() {}
private static class LazyHolder {
private static final MyClass myClass = new MyClass();
}
public static MyClass getInstance() {
return LazyHolder.myClass;
}
}
Однако вы должны поставить большие вопросительные знаки, нужен ли вам синглтон или нет. Часто это не нужно. Просто статическая переменная, перечисление, фабричный класс и / или внедрение зависимостей часто являются лучшим выбором.
Вот еще один способ сделать это:
public enum Singleton{
INSTANCE("xyz", 123);
// Attributes
private String str;
private int i;
// Constructor
Singleton(String str, int i){
this.str = str;
this.i = i;
}
}
Согласно "Эффективной Java" Джоша Блоха, это лучший способ реализации Singleton в Java. В отличие от реализаций, которые включают в себя частное статическое поле экземпляра, которое может быть многократно создано посредством злоупотребления отражением и / или сериализацией, перечисление гарантированно будет одиночным.
Основным ограничением синглетов enum является то, что они всегда создаются во время загрузки класса и не могут быть созданы лениво. Поэтому, если, например, вы хотите создать экземпляр синглтона, используя аргументы времени выполнения, вам придется использовать другую реализацию (предпочтительно с двойной проверкой блокировки).
Есть 3 способа создать синглтон в Java.
нетерпеливая инициализация синглтона
public class Test { private static final Test test = new Test(); private Test() { } public static Test getTest() { return test; } }
ленивая инициализация синглтона (поточно-ориентированная)
public class Test { private static volatile Test test; private Test(){} public static Test getTest() { if(test == null) { synchronized(Test.class) { if(test == null){test = new Test();} } } return test; } }
Билл Пью Синглтон с рисунком держателя (желательно лучший)
public class Test { private Test(){} private static class TestHolder { private static final Test test = new Test(); } public static Test getInstance() { return TestHolder.test; } }
Вот как я это делаю. Это быстрее, потому что это требует только synchronized
блокировать при создании экземпляра.
public class MyClass
{
private static MyClass INSTANCE=null;
private MyClass()
{
}
public static MyClass getInstance()
{
if(INSTANCE==null)
{
synchronized(MyClass.class)
{
if(INSATCNE==null) INSTANCE=new MyClass();
}
}
return INSTANCE;
}
}
Используя ваш пример и используя способ реализации GoF:
public class MyClass{
private static Myclass instance;
private MyClass(){
//Private instantiation
}
public static synchronized MyClass getInstance() //If you want your method thread safe...
{
if (instance == null) {
instance = new MyClass();
}
return instance;
}
}
Надеюсь это поможет:
Ваш класс (оригинальный код, перед редактированием):
public class MyClass {
Myclass myclass;
static { myclass = new MyClass();}
private MyClass(){}
public MyClass getInstance()
{
return myclass;
}
}
не настоящий синглтон:
- поле
myclass
не является частным, может читаться и изменяться извне (при условии, что у вас есть инстаграм, чтобы сделать это на - поле
myclass
не статичен, недоступен в статическом конструкторе (ошибка компиляции) getInstance()
метод не является статическим, поэтому вам нужен экземпляр для его вызова
Фактический код:
public class MyClass {
static Myclass myclass;
static { myclass = new MyClass();}
private MyClass(){}
public static MyClass getInstance()
{
return myclass;
}
}
все еще имеет myclass
не быть личным (ни окончательным)... объявление его окончательным поможет предотвратить непреднамеренное изменение его внутри класса.
private static final Myclass myclass;
public class singletonPattern {
private static singletonPattern obj;
public static singletonPattern getObject() {
return obj = (obj == null) ? new singletonPattern() : obj;
}
public static void main(String args[]) {
singletonPattern sng = singletonPattern.getObject();
}
}
Класс Singloton - это класс, в котором вы каждый раз получаете один и тот же объект. Если вы хотите запретить классу создавать более одного объекта, нам нужен класс Singleton.
Например:
public class Booking {
static Booking b = new Booking();
private Booking() { }
static Booking createObject() { return b; }
}
Для создания объекта этого класса мы можем использовать:
Booking b1, b2, b3, b4;
b1 = Booking.createObject();
b2 = Booking.createObject();
Booking b1, b2, b3, b4;
b1 = Booking.createObject();
b2 = Booking.createObject();
b1
а также b2
ссылаются на один и тот же объект.
Может быть, немного опоздаем с игрой, но базовая реализация будет выглядеть примерно так:
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
Здесь у нас есть класс MySingleton, который имеет закрытый статический член с именем INSTANCE и открытый статический метод с именем getInstance (). При первом вызове getInstance () член INSTANCE имеет значение null. Затем поток перейдет в состояние создания и создаст новый экземпляр класса MySingleton. Последующие вызовы getInstance () обнаружат, что переменная INSTANCE уже установлена, и, следовательно, не создадут другой экземпляр MySingleton. Это гарантирует, что есть только один экземпляр MySingleton, который используется всеми вызывающими getInstance ().
Но у этой реализации есть проблема. У многопоточных приложений будет условие гонки при создании единственного экземпляра. Если несколько потоков выполнения попадают в метод getInstance () одновременно (или около того), каждый из них будет видеть элемент INSTANCE как нулевой. Это приведет к тому, что каждый поток создаст новый экземпляр MySingleton и впоследствии установит элемент INSTANCE.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
Здесь мы использовали ключевое слово synchronized в сигнатуре метода для синхронизации метода getInstance (). Это, безусловно, исправит наше состояние гонки. Теперь потоки будут блокироваться и вводить метод по одному. Но это также создает проблему производительности. Эта реализация не только синхронизирует создание отдельного экземпляра, но и все вызовы getInstance (), включая чтение. Чтения не должны быть синхронизированы, поскольку они просто возвращают значение INSTANCE. Поскольку чтение будет составлять основную часть наших вызовов (помните, что создание экземпляров происходит только при первом вызове), мы подвергнемся ненужному снижению производительности при синхронизации всего метода.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
Здесь мы переместили синхронизацию из сигнатуры метода в синхронизированный блок, который оборачивает создание экземпляра MySingleton. Но решает ли это нашу проблему? Что ж, мы больше не блокируем чтение, но мы также сделали шаг назад. Несколько потоков попадут в метод getInstance () в одно и то же время или около того, и все они увидят элемент INSTANCE как нулевой. Затем они попадут в синхронизированный блок, где можно получить блокировку и создать экземпляр. Когда этот поток выходит из блока, другие потоки будут бороться за блокировку, и один за другим каждый поток будет проходить через блок и создавать новый экземпляр нашего класса. Итак, мы вернулись туда, откуда начали.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Здесь мы выдаем еще одну проверку изнутри блока. Если элемент INSTANCE уже установлен, мы пропустим инициализацию. Это называется двойной проверкой блокировки.
Это решает нашу проблему множественной реализации. Но еще раз, наше решение поставило еще одну проблему. Другие темы могут не "видеть", что элемент INSTANCE обновлен. Это из-за того, как Java оптимизирует операции с памятью. Потоки копируют исходные значения переменных из основной памяти в кэш процессора. Изменения значений затем записываются в этот кэш и считываются из него. Это особенность Java, предназначенная для оптимизации производительности. Но это создает проблему для нашей одноэлементной реализации. Второй поток, обрабатываемый другим процессором или ядром с использованием другого кэша, не увидит изменений, внесенных первым. Это приведет к тому, что второй поток увидит член INSTANCE как ноль, что приведет к созданию нового экземпляра нашего синглтона.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Мы решаем это, используя ключевое слово volatile в объявлении члена INSTANCE. Это скажет компилятору всегда читать и записывать в основную память, а не в кэш процессора.
Но это простое изменение имеет свою цену. Поскольку мы обходим кэш-память ЦП, мы будем снижать производительность каждый раз, когда работаем с изменчивым членом INSTANCE, что мы делаем 4 раза. Мы дважды проверяем существование (1 и 2), устанавливаем значение (3), а затем возвращаем значение (4). Можно утверждать, что этот путь является крайним случаем, поскольку мы создаем экземпляр только во время первого вызова метода. Возможно, удар по производительности на создание терпимо. Но даже наш основной вариант использования, читает, будет работать на volatile член дважды. Один раз проверить существование, и снова вернуть его значение.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
MySingleton result = INSTANCE;
if (result == null) {
synchronized(MySingleton.class) {
result = INSTANCE;
if (result == null) {
INSTANCE = result = createInstance();
}
}
}
return result;
}
Поскольку снижение производительности связано с работой непосредственно на volatile-элементе, давайте установим локальную переменную в значение volatile и вместо этого будем работать с локальной переменной. Это уменьшит количество раз, когда мы работаем с энергозависимым, тем самым восстанавливая некоторые из наших потерянных показателей. Обратите внимание, что мы должны снова установить нашу локальную переменную при входе в синхронизированный блок. Это гарантирует, что он в курсе любых изменений, которые произошли, пока мы ожидали блокировки.
Я недавно написал статью об этом. Деконструкция Синглтона. Вы можете найти больше информации об этих примерах и пример паттерна "holder". Существует также реальный пример, демонстрирующий изменчивый подход с двойной проверкой. Надеюсь это поможет.
При создании одноэлементного класса вы должны подумать о следующих свойствах
- отражение
- Многопоточность
- клон
- Сериализация
если в вашем классе нет интерфейсов Clone или Serialization, я думаю, что следующий класс лучше всего подходит как синглтон-класс.
public class JavaClass1 {
private static JavaClass1 instance = null;
private JavaClass1() {
System.out.println("Creating -------");
if (instance != null) { // For Reflection
throw new RuntimeException("Cannot create, please use getInstance()");
}
}
public static JavaClass1 getInstance() {
if (instance == null) {
createInstance();
}
return instance;
}
private static synchronized void createInstance() { // for multithreading
if (instance == null) {
instance = new JavaClass1();
}
}}