Режим разработки — одноэлементный режим

Шаблоны проектирования

1. Введение

Паттерн проектирования Синглтон (Singleton Pattern) — один из самых простых и распространенных шаблонов проектирования, в своей основной структуре содержит только специальный класс, называемый синглетон. Одноэлементный режим может гарантировать наличие только одного экземпляра класса в системе и легкий доступ к экземпляру из внешнего мира, тем самым облегчая контроль количества экземпляров и экономя системные ресурсы. Если вы хотите иметь в системе только один объект определенного класса, шаблон singleton — лучшее решение, позволяющее избежать логических ошибок в случае множественных экземпляров объектов (количество инстанций можно контролировать).

  • 1. Одноэлементный класс может иметь только один экземпляр.
  • 2. Одноэлементный класс должен сам создавать свой собственный уникальный экземпляр.
  • 3. Одноэлементный класс должен предоставлять этот экземпляр всем другим объектам.

2. Обзор

В Java существует четыре основных типа одноэлементных шаблонов:Существует четыре типа ленивых одиночек, голодных одиночек, зарегистрированных одиночек и синглетонов ThreadLocal..

  • Ленивый человек: это не потокобезопасно и должно контролироваться определенной кокетливой операцией.Отказ от притворства может привести к просмотру Губки Боба Квадратные Штаны в течение недели.
  • Hungry man: Он по своей сути является потокобезопасным, и его экземпляр уже был создан при выполнении ClassLoad.Если эта операция будет слишком кокетливой, это приведет к пустой трате ресурсов.
  • Реестр синглтона: когда Spring инициализирует bean-компонент, синглтон по умолчанию использует этот метод.
  • Режим Singleton реализован в голодном режиме, ленивом режиме, статическом внутреннем классе, перечислении и т. д. Методы построения этих режимов являются закрытыми и не могут быть унаследованы.
  • Зарегистрированные синглетоны делают синглтоны открытыми для наследования
  • ThreadLocal — это форма копирования потока, которая может гарантировать локальный синглтон, то есть синглтон в соответствующем потоке, но синглтон не гарантируется между потоками.

\color{#4285f4}{1.特点}

1. 私有构造方法,只能有一个实例。
2. 私有静态引用指向自己实例,必须是自己在内部创建的唯一实例。
3. 单例类给其它对象提供的都是自己创建的唯一实例

\color{#4285f4}{2.案例}

1. 在计算机系统中,内存、线程、CPU等使用情况都可以再任务管理器中看到,但始终只能打开一个任务管理器,它在Windows操作系统中是具备唯一性的,因为弹多个框多次采集数据浪费性能不说,采集数据存在误差那就有点逗比了不是么…
2. 每台电脑只有一个打印机后台处理程序
3. 线程池的设计一般也是采用单例模式,方便对池中的线程进行控制

\color{#4285f4}{3.注意事项}

 1. 实现方式种类较多,有的非线程安全方式的创建需要特别注意,且在使用的时候尽量根据场景选取较优的,线程安全了还需要去考虑性能问题。
 2. 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
 3. 没有抽象层,扩展有困难。
 4. 职责过重,在一定程度上违背了单一职责原则。
 5. 使用时不能用反射模式创建单例,否则会实例化一个新的对象

3. Откройте Авентюру

\color{#9932CC}{第一种艳遇:}\color{#34a853}{单一检查(懒汉)非线程安全}

public class LazyLoadBalancer {

    private static LazyLoadBalancer loadBalancer;
    private List<String> servers = null;

    private LazyLoadBalancer() {
        servers = new ArrayList<>();
    }

    public void addServer(String server) {
        servers.add(server);
    }

    public String getServer() {
        Random random = new Random();
        int i = random.nextInt(servers.size());
        return servers.get(i);
    }

    public static LazyLoadBalancer getInstance() {
        // 第一步:假设T1,T2两个线程同时进来且满足 loadBalancer == null
        if (loadBalancer == null) {
            // 第二步:那么 loadBalancer 即会被实例化2次
            loadBalancer = new LazyLoadBalancer();
        }
        return loadBalancer;
    }

    public static void main(String[] args) {
        LazyLoadBalancer balancer1 = LazyLoadBalancer.getInstance();
        LazyLoadBalancer balancer2 = LazyLoadBalancer.getInstance();
        System.out.println("hashCode:"+balancer1.hashCode());
        System.out.println("hashCode:"+balancer2.hashCode());
        balancer1.addServer("Server 1");
        balancer2.addServer("Server 2");
        IntStream.range(0, 5).forEach(i -> System.out.println("转发至:" + balancer1.getServer()));
    }
}

анализировать:

Все отлично работает в однопоточной среде,balancer1иbalancer2Хеш-код двух объектов точно такой же, поэтому можно сделать вывод, что в стеке есть только одно содержимое, но в блоке кода существует угроза безопасности потока, потому что отсутствие конкурентных условий, многопоточная конкуренция ресурсов окружающей среды не оптимистичны. , см. Содержание приведенного выше комментария к коду.

\color{#9932CC}{第二种艳遇:}\color{#34a853}{无脑上锁(懒汉)线程安全,性能较差,第一种升级版}

public synchronized static LazyLoadBalancer getInstance() {
    if (loadBalancer == null) {
        loadBalancer = new LazyLoadBalancer();
    }
    return loadBalancer;
}

анализировать:

Несомненно, каждый, кто знает ключевое слово synchronized, знает, что перед снятием блокировки в методе синхронизации ждут другие потоки, думать об этом небезопасно, но в то же время безопасности производительности мало, я просто инициализирую это один раз, и вы будете запирать его каждый раз, когда вы придете, вы не устали (это не важно, это для третьего)..

\color{#9932CC}{第三种艳遇:}\color{#34a853}{双重检查锁(DCL),完全就是前两种的结合体啊,有木有,只是将同步方法升级成了同步代码块}

//划重点了 **volatile**
1 private volatile static LazyLoadBalancer loadBalancer;
2
3 public static LazyLoadBalancer getInstance() {
4    if (loadBalancer == null) {
5        synchronized (LazyLoadBalancer.class) {
6            if (loadBalancer == null) {
7               loadBalancer = new LazyLoadBalancer();
8            }
0        }
10    }
11    return loadBalancer;
12 }

Если нет проблем с volatile: Если вы сначала проверите, что loadBalancer не равен нулю, вам не нужно выполнять следующие операции блокировки и инициализации. Следовательно, потери производительности, вызванные синхронизацией, могут быть значительно снижены. Когда поток выполняется до строки 4, когда код считывает, что loadBalancer не равен null, возможно, объект, на который ссылается loadBalancer, не был инициализирован. Объект создается в строке 7. Эту строку кода можно разбить на следующие 3 строки псевдокода:

memory=allocate(); //1:分配对象的内存空间
ctorInstance(memory); //2:初始化对象
instance=memory; //3:设置instance指向刚分配的内存地址

Между 2 и 3 в приведенных выше 3 строках кода он может быть переупорядочен (на некоторых JIT-компиляторах такое переупорядочение действительно происходит, если вы не понимаете переупорядочение, JMM объяснит подробно позже). Время выполнения после переупорядочения между 2 и 3 выглядит следующим образом.

memory=allocate(); //1:分配对象的内存空间
instance=memory; //3:设置instance指向刚分配的内存地址,注意此时对象还没有被初始化
ctorInstance(memory); //2:初始化对象

Возвращаясь к строке 7 примера кода, если происходит переупорядочивание, другой параллельно выполняющийся поток B может решить, что экземпляр не является нулевым в строке 4. Затем поток B получит доступ к объекту, на который ссылается экземпляр, но этот объект может быть не инициализирован потоком A в это время, что может вызвать NPE.

анализировать:

  1. Предположим, новый LazyLoadBalancer() загружает слишком много контента
  2. loadBalancer не пуст раньше времени из-за перестановки
  3. Просто случилось так, что другие потоки заметили, что объект не является нулевым и напрямую возвращается для использования редкого одноэлементного нулевого указателя, который внезапно попал в цель.
  • Существует проблема:Прежде всего, мы должны уяснить, что DCL не может гарантировать потокобезопасность.Это ясно тем, кто немного разбирается в JVM.По сравнению с C/C++, в нем всегда отсутствует формальная модель памяти, поэтому для повышения производительности он также выполнит операцию перестановки инструкций, в это время loadBalancer не будет заранее пустым, и другие потоки просто наблюдают за тем, что объект не пустой и напрямую возвращается для использования (но на самом деле в нем еще есть какое-то содержимое которые не были загружены)
  • решение:Используйте volatile для изменения loadBalancer, потому что переменные-члены, измененные volatile, могут гарантировать, что несколько потоков могут обрабатываться последовательно, что защитит оптимизацию производительности, вызванную перестановкой инструкций JVM.

\color{#9932CC}{第四种艳遇:}\color{#34a853}{Demand Holder,静态内部类 (懒汉)线程安全,推荐使用}

private LazyLoadBalancer() {}

private static class LoadBalancerHolder {
    //在JVM中 final 对象只会被实例化一次,无法修改
    private final static LazyLoadBalancer INSTANCE = new LazyLoadBalancer();
}

public static LazyLoadBalancer getInstance() {
    return LoadBalancerHolder.INSTANCE;
}

анализировать:

потребитель спроса, мы добавляем статический внутренний класс в LazyLoadBalancer, создаем одноэлементный объект во внутреннем классе, а затем Одноэлементный объект возвращается для внешнего использования с помощью метода getInstance(), поскольку статический одноэлементный объект не функционирует какLazyLoadBalancerПеременные-члены создаются напрямую, а экземпляр LoadBalancerHolder не создается при загрузке класса.Таким образом, обе ленивые загрузки могут быть достигнуты, и может обеспечить безопасность потоков, не влияя на производительность системы (необходимое лекарство в путешествии домой).

Версия блокировки с двойной проверкой, независимо от того, насколько выше производительность, по-прежнему использует синхронизированный модификатор.Поскольку этот модификатор используется, он будет иметь некоторое влияние на производительность, поэтому родился Demand Holder, включающий механизм загрузки внутренних классов, Для обзора код выглядит следующим образом:

package test;

public class OuterTest {

    static {
        System.out.println("load outer class...");
    }

    // 静态内部类
    static class StaticInnerTest {
        static {
            System.out.println("load static inner class...");
        }

        static void staticInnerMethod() {
            System.out.println("static inner method...");
        }
    }

    public static void main(String[] args) {
        OuterTest outerTest = new OuterTest(); // 此刻其内部类是否也会被加载?
        System.out.println("===========分割线===========");
        OuterTest.StaticInnerTest.staticInnerMethod(); // 调用内部类的静态方法
    }

}

Результат выглядит следующим образом:

load outer class...
===========分割线===========
load static inner class...
static inner method

Таким образом, мы имеем следующеев заключении:

1. 加载一个类时,其内部类不会同时被加载。
2. 一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。

\color{#9932CC}{第五种艳遇:}\color{#34a853}{懒汉式,  防止反射|序列化|反序列化}

package singleton;

import java.io.Serializable;

public class LazySingleton4 implements Serializable {

    private static boolean initialized = false;

    private LazySingleton4() {
        synchronized (LazySingleton4.class) {
            if (initialized == false) {
                initialized = !initialized;
            } else {
                throw new RuntimeException("单例已被破坏");
            }
        }
    }

    static class SingletonHolder {
        private static final LazySingleton4 instance = new LazySingleton4();
    }

    public static LazySingleton4 getInstance() {
        return SingletonHolder.instance;
    }
    
    
    //序列化 防止序列化被破坏单例
    private Object readResolve() {
        return getInstance();
    }
}

анализировать:

1. Мы знаем, что отражение может создавать объекты, затем мы не позволяем отражению разрушать синглтон по принципу отражения, поэтому родился синглтон, как указано выше.

2. В распределенных системах в некоторых случаях необходимо реализовать интерфейс Serializable в классе singleton. Таким образом, вы можете сохранить его состояние в файловой системе и получить его позже. Чтобы избежать этой проблемы, нам нужно предоставить реализацию метода readResolve(). readResolve() заменяет чтение объекта из потока. Это гарантирует, что никто не сможет создавать новые экземпляры во время сериализации и десериализации.

Почему десериализация может разрушить Давайте посмотрим на исходный код ois.readObject():

private Object readObject0(boolean unshared) throws IOException {
	...省略
	case TC_OBJECT:
	  return checkResolve(readOrdinaryObject(unshared));
}
-------------------------------------------------------------------
private Object readOrdinaryObject(boolean unshared){
	if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
	//重点!!!
	//首先isInstantiable()判断是否可以初始化
	//如果为true,则调用newInstance()方法创建对象,这时创建的对象是不走构造函数的,是一个新的对象
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }

        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        handles.finish(passHandle);
	
	//重点!!!
	//hasReadResolveMethod()会去判断,我们的InnerClassSingleton对象中是否有readResolve()方法
        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
	//如果为true,则执行readResolve()方法,而我们在自己的readResolve()方法中 直接retrun INSTANCE,所以还是返回的同一个对象,保证了单例
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
}

\color{#9932CC}{第六种艳遇:}\color{#34a853}{枚举特性(懒汉)线程安全,推荐使用}

enum Lazy {
    INSTANCE;
    private LazyLoadBalancer loadBalancer;

	//枚举的特性,在JVM中只会被实例化一次
    Lazy() {
        loadBalancer = new LazyLoadBalancer();
    }

    public LazyLoadBalancer getInstance() {
        return loadBalancer;
    }
}

анализировать:

По сравнению с предыдущим, этот метод также использует функцию JAVA: класс перечисления гарантированно имеет только один экземпляр (даже если используется механизм отражения, перечисление не может быть создано несколько раз).

\color{#9932CC}{第七种艳遇:}\color{#34a853}{饿汉单例(天生线程安全)}

public class EagerLoadBalancer {
    private final static EagerLoadBalancer INSTANCE = new EagerLoadBalancer();

    private EagerLoadBalancer() {}

    public static EagerLoadBalancer getInstance() {
        return INSTANCE;
    }
}

анализировать:

Используя механизм ClassLoad, инстанцирование выполняется во время загрузки, в то время как статические методы инициализируются только один раз во время компиляции, и существует только один объект. При использовании он был инициализирован и может быть вызван напрямую, но по сравнению с ленивым режимом, он самый быстрый при использовании, но эта штука как яма, вырытая самостоятельно, и вы должны прыгать, когда плачете, вы не нужно инициализировать копию в памяти Там дырка посередине... Но писать просто~~ ах~~

\color{#9932CC}{第八种艳遇:}\color{#34a853}{登记式单例}


public class RegistSingleton {
    //用ConcurrentHashMap来维护映射关系,这是线程安全的
    public static final Map<String,Object> REGIST=new ConcurrentHashMap<String, Object>();
    static {
        //把RegistSingleton自己也纳入容器管理
        RegistSingleton registSingleton=new RegistSingleton();
        REGIST.put(registSingleton.getClass().getName(),registSingleton);
    }
    private RegistSingleton(){}
    public static Object getInstance(String className){
        //如果传入的类名为空,就返回RegistSingleton实例
        if(className==null)
            className=RegistSingleton.class.getName();
            //如果没有登记就用反射new一个
        if (!REGIST.containsKey(className)){
            //没有登记就进入同步块
            synchronized (RegistSingleton.class){
            //再次检测是否登记
                if (!REGIST.containsKey(className)){
                    try {
                    //实例化对象
                        REGIST.put(className,Class.forName(className).newInstance());
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        //返回单例
        return REGIST.get(className);
    }
}

Пройди тест:

public class Main {
    static CyclicBarrier cyclicBarrier=new CyclicBarrier(1000);
    public static void main(String[] args) {
        for (int i = 0; i <1000 ; i++) {
            int n = i;
            new Thread(()->{
                System.out.println("线程"+ n +"准备就绪");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println(RegistSingleton.getInstance("singletonpattern.regist.ClassA"));
            }).start();
        }
    }
}

Выходной результат: потокобезопасный (ClassA — это пустой класс, в котором ничего нет)

Давайте посмотрим на исходный код Spring:

public abstract class AbstractBeanFactory implements ConfigurableBeanFactory{    
   /**  
    * 充当了Bean实例的缓存,实现方式和单例注册表相同  
    */    
   private final Map singletonCache=new HashMap();    
   public Object getBean(String name)throws BeansException{    
       return getBean(name,null,null);    
   }    
...    
   public Object getBean(String name,Class requiredType,Object[] args)throws BeansException{    
      //对传入的Bean name稍做处理,防止传入的Bean name名有非法字符(或则做转码)    
      String beanName=transformedBeanName(name);    
      Object bean=null;    
      //手工检测单例注册表    
      Object sharedInstance=null;    
      //使用了代码锁定同步块,原理和同步方法相似,但是这种写法效率更高    
      synchronized(this.singletonCache){    
         sharedInstance=this.singletonCache.get(beanName);    
       }    
      if(sharedInstance!=null){    
         ...    
         //返回合适的缓存Bean实例    
         bean=getObjectForSharedInstance(name,sharedInstance);    
      }else{    
        ...    
        //取得Bean的定义    
        RootBeanDefinition mergedBeanDefinition=getMergedBeanDefinition(beanName,false);    
         ...    
        //根据Bean定义判断,此判断依据通常来自于组件配置文件的单例属性开关    
        //<bean id="date" class="java.util.Date" scope="singleton"/>    
        //如果是单例,做如下处理    
        if(mergedBeanDefinition.isSingleton()){    
           synchronized(this.singletonCache){    
            //再次检测单例注册表    
             sharedInstance=this.singletonCache.get(beanName);    
             if(sharedInstance==null){    
                ...    
               try {    
                  //真正创建Bean实例    
                  sharedInstance=createBean(beanName,mergedBeanDefinition,args);    
                  //向单例注册表注册Bean实例    
                   addSingleton(beanName,sharedInstance);    
               }catch (Exception ex) {    
                  ...    
               }finally{    
                  ...    
              }    
             }    
           }    
          bean=getObjectForSharedInstance(name,sharedInstance);    
        }    
       //如果是非单例,即prototpye,每次都要新创建一个Bean实例    
       //<bean id="date" class="java.util.Date" scope="prototype"/>    
       else{    
          bean=createBean(beanName,mergedBeanDefinition,args);    
       }    
}    
...    
   return bean;    
}    
}

анализировать:

Зарегистрированный синглтон фактически поддерживает набор экземпляров класса синглтона и сохраняет эти экземпляры в карте (книге регистрации).Для зарегистрированных синглетонов они возвращаются непосредственно с фабрики.Сначала зарегистрируйте, затем верните

  1. Реализовать реестр с помощью карты;
  2. Используйте Protect для изменения конструктора;

Иногда мы не хотим писать класс как одноэлементный шаблон в начале, но когда мы его используем, мы можем использовать его как одноэлементный.

Самый типичный пример — Spring, тип которого по умолчанию — singleton.Как Spring превращает класс, который не является синглтоном, в синглтон?

Здесь используется зарегистрированный синглтон

На самом деле зарегистрированный синглтон не меняет класс. Он играет роль регистрации. Если он не зарегистрирован, он зарегистрирует его для вас и сохранит сгенерированный экземпляр. В следующий раз, когда вы захотите его использовать, дайте это непосредственно вам.

Это то, что делает IOC-контейнер: вы можете попросить его получить его, когда вам это нужно, и он может легко управлять Bean-компонентами.

\color{#9932CC}{第九种艳遇:}\color{#34a853}{ ThreadLocal 局部单例}

public class Singleton {
    
    private Singleton(){}
    
    private static final ThreadLocal<Singleton> threadLocal = 
            new ThreadLocal<Singleton>(){
                @Override
                protected Singleton initialValue(){
                    return new Singleton();
                }
            };
    
    public static Singleton getInstance(){
        return threadLocal.get();
    }
    
}

анализировать:

Этот метод записи использует преимущества характеристик ThreadLocal для обеспечения локальных одиночек, то есть одиночек в соответствующих потоках, но одиночки не гарантируются между потоками.

InitialValue() обычно используется для перезаписи при использовании.Если get вызывается, когда нет набора, метод initialValue будет вызываться для инициализации содержимого.

ThreadLocal предоставит каждому потоку независимую копию переменной, тем самым изолируя конфликт доступа нескольких потоков к данным. Для проблемы многопоточного совместного использования ресурсов механизм синхронизации принимает метод «обмена времени на пространство», в то время как ThreadLocal использует метод «обмена пространства на время». Первый предоставляет переменную только для разных потоков, ставящихся в очередь на доступ, а второй предоставляет переменную для каждого потока, то есть изоляцию потоков, поэтому к ним можно обращаться одновременно, не влияя друг на друга.

\color{#9932CC}{第十种艳遇:}\color{#34a853}{ 使用CAS锁实现(线程安全)}

/**
 * 更加优美的Singleton, 线程安全的
 */
public class Singleton {
 /** 利用AtomicReference */
 private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();
 /**
  * 私有化
  */
 private Singleton(){
 }
 /**
  * 用CAS确保线程安全
  */
 public static final Singleton getInstance(){
  for (;;) {
   Singleton current = INSTANCE.get();
            if (current != null) {
                return current;
            }
            current = new Singleton();
            if (INSTANCE.compareAndSet(null, current)) {
                return current;
            }
        }
 }
 
 public static void main(String[] args) {
  Singleton singleton1 = Singleton.getInstance();
  Singleton singleton2 = Singleton.getInstance();
     System.out.println(singleton1 == singleton2);
 }
}

анализировать:


CAS является потокобезопасным и использует программирование без блокировок.Таким образом, когда большое количество потоков используется для получения экземпляров, это приведет к сжиганию страсти ЦП~

4. Резюме

В этой статье представлены несколько версий шаблона singleton для использования в наших проектах. На самом деле мы обычно начинаем сПриключения Четыре, Пять, Шесть, вы можете выбрать один из трех в зависимости от реальной ситуации. В конце концов, я надеюсь, что вы все что-то выиграете.