Это 14-й день моего участия в августовском испытании обновлений.Подробности о событии:Испытание августовского обновления
1. Одноэлементный режим
Убедитесь, что существует только один экземпляр класса, и предоставьте глобальную точку доступа к этому экземпляру.
1.1 Голодная китайская безопасность
public class Hungry {
private static Hungry hungry = new Hungry();
private Hungry(){
}
public static Hungry getInstance(){
return hungry;
}
}
- пустая трата ресурсов
1.2 Ленивый - поток небезопасен
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){
}
public static LazyMan getInstance(){
if(lazyMan == null){
return lazyMan = new LazyMan();
}
return lazyMan;
}
}
- Объект создается лениво, и объект не будет создан, если класс не используется, тем самым экономя ресурсы;
- Эта реализация небезопасна для потоков, если несколько потоков входят в одно и то же время.
if(lazyMan) == null
, а lazyMan в это время имеет значение null, тогда будет выполняться несколько потоковreturn new LazyMan()
оператор, который вызовет несколько экземпляровlazyMan
.
1.3 Double Checked Lock (DCL) — потокобезопасность
public class LazyMan_Lock {
private volatile static LazyMan_Lock lazyMan_lock ;
private LazyMan_Lock(){
}
public static LazyMan_Lock getInstance(){
if(lazyMan_lock == null){
synchronized (LazyMan_Lock.class){
if(lazyMan_lock == null){
return lazyMan_lock = new LazyMan_Lock();
}
}
}
return lazyMan_lock;
}
}
Если есть только один, если суждение
if(lazyMan_lock == null){
synchronized (LazyMan_Lock.class){
return lazyMan_lock = new LazyMan_Lock();
}
}
-
если только один
if语句
судья, вlazyMan_lock == null
В случае , если два потока вводят оператор if одновременно, то оба потока войдут в блок операторов if. Несмотря на то, что в блоке операторов if есть операция блокировки, оба потока будут выполняться.lazyMan_lock = new LazyMan_Lock();
Этот оператор является просто вопросом последовательности, поэтому он будет создан дважды.Поэтому необходимо использовать замки с двойной проверкой., то есть вам нужно использовать два оператора if: первый оператор if используется, чтобы избежатьlazyMan_lock
Операция блокировки после нее не создается; второй оператор if заблокирован, поэтому может войти только один поток, и он не появитсяlazyMan_lock == null
Когда два потока создаются одновременно; -
lazyMan_lock
использоватьvolatile
Модификация ключевых слов также необходима.lazyMan_lock = new LazyMan_Lock();
Этот код фактически разделен на три шага:- для
lazyMan_lock
Выделить место в памяти; - инициализация
lazyMan_lock
; - Буду
lazyMan_lock
указывает на выделенный адрес памяти;
- для
Однако из-за особенностей перестановки команд JVM порядок выполнения может стать 1-3-2. Перестановка инструкций не является проблемой в однопоточной среде, но в многопоточной среде может привести к тому, что поток получит экземпляр, который еще не был инициализирован. Например, поток T1 выполняет 1 и 3, когда T2 вызываетgetInstance()
позже узналlazyMan_lock
не ноль, поэтому вернитесьlazyMan_lock
, но в это времяlazyMan_lock
не был инициализирован.
Использование volatile может запретить перестановку инструкций JVM и обеспечить нормальную работу в многопоточной среде.
1.4 Статический внутренний класс реализует синглтон
public class Holder {
public Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
private static class InnerClass{
private static final Holder HOLDER = new Holder();
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Holder holder = Holder.getInstance();
Class clazz = Holder.class;
Constructor<Holder> constructor = (Constructor<Holder>) clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
Holder holder1 = (Holder) constructor.newInstance();
System.out.println(holder == holder1);
}
}
- когда
Holder
Когда класс загружается, статический внутренний классInnerClass
не загружается внутри, только при вызовеgetInstance()
метод запускаInnerClass.HOLDER
ВремяInnerClass
будет загружен, инициализированHolder
экземпляр, а JVM может гарантировать, что экземпляр HOLDER будет создан только один раз; - Преимущество этого подхода заключается не только в ленивой инициализации, но и в том, что JVM обеспечивает безопасность потоков.
1.5 Перечисление реализует синглтон
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class EnumSingleTest{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Class clazz = EnumSingle.class;
Constructor<EnumSingle> c = clazz.getDeclaredConstructor(String.class,int.class);
c.setAccessible(true);
EnumSingle instance2 = c.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
========抛出异常
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.xiaojian.single.EnumSingleTest.main(EnumSingle.java:25)
-
Вы можете запретить отражению уничтожать синглтоны, и использование отражения для получения экземпляра вызовет исключение:
Cannot reflectively create enum objects
. За подробностями обращайтесь к исходному коду метода newInstance() экземпляра конструкции отражения.