Singleton pattern - the most simple design pattern?

A. Said in front

In the design of systems development, there will always be just a few cases, ① frequent need to create objects destroyed, ② create objects need to consume a lot of resources, but frequently used objects objects (objects such as tools, databases or frequently accessed files , data source, session factories, etc.); ③ a class has only one object, such as application-based applications; then they should consider using the singleton. Personal blog address www.mycookies.cn

II. Motivation Singleton pattern

  • In software systems, often have some special classes, they must ensure that in the system there is only one instance , in order to ensure their logical correctness, and good efficiency
  • How to bypass the conventional construction, providing the mechanism in order to ensure that only one instance of a class? It should be the responsibility of the design class, rather than the user's responsibility

III. Schema definition

确保类只有一个实例,并提供全局访问点。

Hungry Chinese-style

After the completion of the class initialization is complete object is created, regardless of whether the advance should be instantiated.

/**
 * 饿汉式[工厂方法]
 *
 * @author Jann Lee
 * @date 2019-07-21 14:28
 */
public class Singleton1 {

    private static final Singleton1 instance = new Singleton1();

    private Singleton1() {
    }

    public static Singleton1 getInstance() {
        return instance;
    }
}

 /**
 * 饿汉式[公有域]
 */
public class Singleton2 {
    
    public static final Singleton2 instance = new Singleton2();
    
    private Singleton2() {
    }
}
 /**
 * 饿汉式[静态代码块,工厂方法]
 */
public class Singleton3 {

    private static Singleton3 instance;

    static {
        instance = new Singleton3();
    }

    private Singleton3() {
    }

    public static Singleton3 getInstance() {
        return instance;
    }
}

These three are only differences in the wording of the code to achieve, is the most common implementation, "a hungry man style."

Advantages : is enclosed within a complete instantiation time, avoid multi-threading issues
drawback : may cause memory waste (examples might never be used to)

Lazy style

As the name suggests, because I was lazy, so when used will be instantiated.

Realization of ideas: privatization constructor -> declare member variables -> [If you provide public access to the member variable is not empty, the direct return, if space is created for the return]

/**
 * 1.懒汉式[非线程安全]
 *
 * @author Jann Lee
 * @date 2019-07-21 14:31
 **/
public class Singleton1 {

    private static Singleton1 instance;

    private Singleton1() {
    }

    public static Singleton1 getInstance() {
        if (instance == null) {
            instance = new Singleton1();
        }
        return instance;
    }
}

Implementations described above is the most easily think of, but it should also be regarded as a wrong implementation, because under more multithreaded environment of an object may have been created several times.

In order to solve thread safety issues, will be synchronized methods

/**
 * 2.懒汉式[同步方法]
 **/
public class Singleton2 {

    private static Singleton2 instance;

    private Singleton2() {
    }
    
   /**
     * 同步方法,保证线程安全
     */
    public static synchronized Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}

Indeed, this realization to solve the problems caused by the concurrent circumstances. But every acquisition objects need to be synchronized, but objects can be created only once; because concurrent reads data need not be synchronized, so this method in the synchronization is not necessary, is sure to bring in concurrency loss in performance.

At this time we first think of narrowing the range of synchronization, synchronize only use when creating the objects that use synchronized block implementation.

/**
 * 4.懒汉式[同步代码块]
 **/
public class Singleton3 {

    private static Singleton3 instance;

    private Singleton3() {
    }

    /**
     * 同步代码块,并不能保证线程安全
     */
    public static Singleton3 getInstance() {
        if (instance == null) {
            synchronized (Singleton3.class) {
                instance = new Singleton3();
            }
        }
        return instance;
    }
}

这是一种错误的实现方式,虽然减少了加锁范围,但是又回到了并发环境下的重复创建对象的问题,具体分析思路可以参考上文中的图。两个线程在if (instance==null)之后发生了竞争,线程1获取到了锁,线程2挂起,执行完毕后释放锁,此时线程二获取到锁后回接着往下执行,再次创建对象。

解决上述问题,我们只需要在同步代码块中加入校验。

/**
 * 懒汉式[双重检查锁]
 **/
public class Singleton4 {

    private static volatile Singleton4 instance;

    private Singleton4() {
    }

    public static Singleton4 getInstance() {
        if (instance == null) {
            synchronized (Singleton4.class) {
                if (instance == null) {
                    instance = new Singleton4();
                }
            }
        }
        return instance;
    }
}

注意:此处除了再次校验是否为空之外,还给成员变量之前加上了一个volatile关键字,这个非常重要。因为在代码执行过程中会发生指令重排序。这里你只需要知道加上volatile之后能保证指令不会被重排序,程序能正确执行,而不加则可能不出错。

在编译器和处理器为了提高程序的运行性能,会对指令进行重新排序。即代码会被编译成操作指令,而指令执行顺序可能会发生变化。

java中创建对象分为 三个步骤【可以简单理解为三条指令】

  1. 分配内存,内存空间初始化
  2. 对象初始化,类的元数据信息,hashCode等信息
  3. 将内存地址返回

如果2,3顺序发生了变化,另一个线程获得锁时恰好还没有完成对象初始化,即instance指向null,就会重复创建对象。

静态内部类

在静态内部类中持有一个对象。

/**
 * 使用静态内部类实现单例模式
 *
 * @author Jann Lee
 * @date 2019-07-21 14:47
 **/
public class Singleton {

    private Singleton (){}

    public static Singleton getInstance() {
        return ClassHolder.singleton;
    }

    /**
     * Singleton装载完成后,不会创建对象
     * 调用getInstance时候,静态内部类ClassHolder才进行装载
     */
    private static class ClassHolder {
       private static final Singleton singleton = new Singleton();
    }
}

静态内部类的实现则是根据java语言特性实现的,即让静态内部类持有一个对象;根据类加载机机制的特点,每个类只会加载一次,并且只有调用getInstance方法时才会加载内部类。这样就能保证对象只被创建一次,无论是否在多线程环境中。

只包含单个元素的枚举类型

在java中枚举也是类的一种,实际上枚举的每一个元素都是一个枚举类的对象,可以理解为对类的一种封装,默认私有构造方法,且不能使用public修饰。

/**
 * 枚举实现单例模式
 *
 * 为了便于理解给枚举类添加了两个属性
 * @author Jann Lee
 * @date 2019-07-21 14:55
 **/
public enum Singleton {

    INSTANCE;

    private String name;

    private int age;

    Singleton04() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

四.要点总结

  • SIngleton模式中的实例构造器可以设置为protected以允许字类派生
  • Singleton模式一般不要支持拷贝构造函数和clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背。
  • 如何实现多线程环境下安全的Singleton,注意对双重检查锁的正确实现。

五.思考

  1. java中创建对象的方式有很多种,其中当然包括反射,反序列化,那么上述各种设计模式还能保证对象只会被创建一次吗?(这个问题会在下一篇 中进行分析)
  2. volatile关键字是一个非常重要的关键字,它有那些功能?

Guess you like

Origin www.cnblogs.com/liqiangchn/p/11222827.html