Шаблоны проектирования Java (1) — шаблон Singleton

Java Шаблоны проектирования
Шаблоны проектирования Java (1) — шаблон Singleton

Это 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() экземпляра конструкции отражения.

    Snipaste_2021-05-13_21-16-17