Потокобезопасность в одиночных классах Java

Одиночка – один из наиболее широко используемых шаблонов проектирования для создания объектов в приложениях. Если вы используете его в многопоточной среде, безопасность потоков для одиночного класса становится очень важной. В реальных приложениях ресурсы, такие как подключения к базе данных или информационные системы предприятий (EIS), ограничены и должны использоваться с умом, чтобы избежать нехватки ресурсов. Для достижения этой цели мы можем реализовать шаблон проектирования Одиночка. Мы можем создать обертывающий класс для ресурса и ограничить количество создаваемых объектов во время выполнения до одного.

Thread Safe Singleton в Java

В общем случае мы следуем нижеуказанным шагам для создания одиночного класса:

  1. Создайте закрытый конструктор, чтобы избежать создания нового объекта с использованием оператора new.
  2. Объявите закрытый статический экземпляр того же класса.
  3. публичный статический метод, который будет возвращать переменную экземпляра класса 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?

Существует три способа обеспечения потокобезопасности.

  1. Создайте переменную экземпляра во время загрузки класса.
    Преимущества:
  • Безопасность потока без синхронизации
  • Легко реализовать

Недостатки:

  • Раннее создание ресурса, который может не использоваться в приложении.
  • Приложение-клиент не может передавать аргументы, поэтому мы не можем их повторно использовать. Например, у нас есть общий класс Singleton для подключения к базе данных, где приложение-клиент предоставляет свойства сервера базы данных.
  1. Синхронизировать метод getInstance().
    Преимущества:
  • Потоковая безопасность гарантирована.
  • Клиентское приложение может передавать параметры
  • Ленивая инициализация достигнута

Минусы:

  • Медленная производительность из-за накладных расходов на блокировку.
  • Ненужная синхронизация, которая больше не требуется после инициализации переменной экземпляра.
  1. Используйте синхронизированный блок внутри цикла 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