单例模式

单例模式(Singleton),是一种常用的设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。

单例模式一般符合以下三个条件:

  • 私有的构造方法;

  • 指向自己实例的私有静态引用;

  • 以自己实例为返回值的静态的公有方法。

 

单例一般具有以下几种实现方法

1、饿汉式(立即加载)

// 饿汉式单例
public class Singleton {
 
    // 指向自己实例的私有静态引用,主动创建
    private static Singleton singleton = new Singleton();
 
    // 私有的构造方法
    private Singleton(){}
 
    // 以自己实例为返回值的静态的公有方法,静态工厂方法
    public static Singleton getSingleton(){
        return singleton;
    }
}

这种方法是类被加载时候,就实例化一个对象并交给自己的引用,即使在多线程下也是线程安全的,因为类只会被加载一次,缺点就是资源效率不高,可能getInstance()永远不会执行到。

2、懒汉式(延迟加载)

// 懒汉式单例
public class Singleton {
 
    // 指向自己实例的私有静态引用
    private static Singleton singleton;
 
    // 私有的构造方法
    private Singleton(){}
 
    // 以自己实例为返回值的静态的公有方法,静态工厂方法
    public static Singleton getSingleton(){
        // 被动创建,在真正需要使用时才去创建
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

这种方法资源利用率高,不执行getInstance()就不会被实例,但是在多线程下,该方法存在线程安全问题。

3、线程安全的懒汉式

// 线程安全的懒汉式单例
public class Singleton {
 
    private static Singleton singleton;
 
    private Singleton(){}
 
    // 使用 synchronized 修饰,临界资源的同步互斥访问
    public static synchronized Singleton getSingleton(){
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

该实现与上面传统懒汉式单例的实现唯一的差别就在于:是否使用 synchronized 修饰 getSingleton()方法。若使用,就保证了对临界资源的同步互斥访问,也就保证了单例。从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率会很低,因为同步块的作用域有点大,而且锁的粒度有点粗。同步方法效率低。

4、改进的线程安全的懒汉式

// 线程安全的懒汉式单例
public class Singleton {
 
    //使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
    private static volatile Singleton singleton;
 
    private Singleton() {
    }
    public static Singleton getSingleton() {
        // Double-Check idiom
        if (singleton == null) {
            synchronized (Singleton.class) {       // 1
                // 只需在第一次创建实例时才同步
                if (singleton == null) {       // 2
                    singleton = new Singleton();      // 3
                }
            }
        }
        return singleton3;
    }
}

使用双重检查保证了只有第一次创建的时候才会进入同步块,之后获取单例的时候不需要再去获取同步锁。但是这里要特别注意必须使用volatile关键字修饰单例引用。

因为
做第一次检查的时候,如果singleton!= null,并不代表singleton一定已经初始化完成了,造成这种情形的原因是指令重排序;

singleton = new Singleton(); 这句在底层经历了3个动作:

1.分配内存给这个对象;
2.初始化对象;
3.设置 singleton 指向刚分配的内存;
这3个动作中,2和3的动作可能颠倒,其造成的结果就是:Thread-0第一次检查的时候,由于Thread-1先执行3,singleton 指向刚分配的内存,导致Thread-0看到的 singleton 不为空,直接返回 singleton,但此时singleton 在Thread-1中还没有初始化,所以造成程序出问题;

用 volatile 修饰 lazyDoubleCheckSingleton,就禁止了重排序;

5、静态内部类(延迟加载

// 线程安全的懒汉式单例
public class Singleton5 {
 
    // 私有内部类,按需加载,用时加载,也就是延迟加载
    private static class Holder {
        private static Singleton5 singleton5 = new Singleton5();
    }
 
    private Singleton5() {
 
    }
 
    public static Singleton5 getSingleton5() {
        return Holder.singleton5;
    }
}

该方法利用了JVM加载一个类时,其内部类不会同时被加载。达到了延迟加载的目的,又不会由线程安全问题

6、使用枚举

public enum  EnumSingleton {
    INSTANCE;
    public EnumSingleton getInstance(){
        return INSTANCE;
    }
}

上面的五种方法都可以利用反射实例化出另一个实例,存在安全问题,使用枚举则不会。

posted @ 2019-05-20 21:11  梦天幻  阅读(172)  评论(0编辑  收藏  举报