Одиночка – один из наиболее широко используемых шаблонов проектирования для создания объектов в приложениях. Если вы используете его в многопоточной среде, безопасность потоков для одиночного класса становится очень важной. В реальных приложениях ресурсы, такие как подключения к базе данных или информационные системы предприятий (EIS), ограничены и должны использоваться с умом, чтобы избежать нехватки ресурсов. Для достижения этой цели мы можем реализовать шаблон проектирования Одиночка. Мы можем создать обертывающий класс для ресурса и ограничить количество создаваемых объектов во время выполнения до одного.
Thread Safe Singleton в Java
В общем случае мы следуем нижеуказанным шагам для создания одиночного класса:
- Создайте закрытый конструктор, чтобы избежать создания нового объекта с использованием оператора new.
- Объявите закрытый статический экземпляр того же класса.
- публичный статический метод, который будет возвращать переменную экземпляра класса Singleton. Если переменная не инициализирована, то инициализировать её, в противном случае просто вернуть переменную экземпляра.
Используя вышеуказанные шаги, я создал одиночный класс, который выглядит следующим образом. ASingleton.java
package com.journaldev.designpatterns;
public class ASingleton {
private static ASingleton instance = null;
private ASingleton() {
}
public static ASingleton getInstance() {
if (instance == null) {
instance = new ASingleton();
}
return instance;
}
}
В вышеуказанном коде метод getInstance() не является потокобезопасным. Несколько потоков могут обращаться к нему одновременно. Для первых нескольких потоков, когда переменная экземпляра не инициализирована, несколько потоков могут войти в условие if и создать несколько экземпляров. Это нарушит нашу реализацию синглтона.
Как достичь потокобезопасности в классе Singleton?
Существует три способа обеспечения потокобезопасности.
- Создайте переменную экземпляра во время загрузки класса.
Преимущества:
- Безопасность потока без синхронизации
- Легко реализовать
Недостатки:
- Раннее создание ресурса, который может не использоваться в приложении.
- Приложение-клиент не может передавать аргументы, поэтому мы не можем их повторно использовать. Например, у нас есть общий класс Singleton для подключения к базе данных, где приложение-клиент предоставляет свойства сервера базы данных.
- Синхронизировать метод getInstance().
Преимущества:
- Потоковая безопасность гарантирована.
- Клиентское приложение может передавать параметры
- Ленивая инициализация достигнута
Минусы:
- Медленная производительность из-за накладных расходов на блокировку.
- Ненужная синхронизация, которая больше не требуется после инициализации переменной экземпляра.
- Используйте синхронизированный блок внутри цикла if и волатильную переменную
Плюсы:
- Потоковая безопасность гарантирована
- Клиентское приложение может передавать аргументы
- Ленивая инициализация достигнута
- Перегрузка синхронизации минимальна и применима только для первых нескольких потоков, когда переменная равна null.
Минусы:
- Дополнительное условие if
Рассматривая все три способа обеспечения потокобезопасности, я думаю, что третий – лучший вариант. В этом случае измененный класс будет выглядеть следующим образом:
package com.journaldev.designpatterns;
public class ASingleton {
private static volatile ASingleton instance;
private static Object mutex = new Object();
private ASingleton() {
}
public static ASingleton getInstance() {
ASingleton result = instance;
if (result == null) {
synchronized (mutex) {
result = instance;
if (result == null)
instance = result = new ASingleton();
}
}
return result;
}
}
Локальная переменная result
кажется ненужной. Но она присутствует, чтобы улучшить производительность нашего кода. В случаях, когда экземпляр уже инициализирован (что происходит в большинстве случаев), волатильное поле доступно только один раз (из-за “return result;” вместо “return instance;”). Это может улучшить общую производительность метода на 25 процентов. Если вы считаете, что есть лучшие способы достижения этого или если потоковая безопасность компрометирована в вышеуказанной реализации, пожалуйста, прокомментируйте и поделитесь этим с нами всеми.
Бонусный совет
String не является очень хорошим кандидатом для использования с ключевым словом synchronized. Это потому, что они хранятся в пуле строк, и мы не хотим блокировать строку, которая может использоваться другим участком кода. Поэтому я использую переменную Object. Узнайте больше о синхронизации и безопасности потоков в Java.
Вы можете посмотреть больше примеров на Java в нашем репозитории GitHub.
Source:
https://www.digitalocean.com/community/tutorials/thread-safety-in-java-singleton-classes