Java中如何实现单例模式

Java中,单例模式一般有2种分类饿汉模式和懒汉模式。java

饿汉模式指的是单例实例在类装载时就被建立了。缓存

懒汉方式值的是单例实例在首次使用时才被建立。安全

不管是饿汉模式仍是懒汉模式,都是用了一个静态成员变量来存放真正的实例。而且私有化构造函数,防止被外部实例化。多线程

单例(饿汉模式)代码:ide

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

    //私有化构造方法,防止被实例化   
    private Singleton() {
    }

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


单例懒汉模式代码,注意静态字段声明的时候,有一个volatile关键字,而且代码中两次判断是不是null,这种双重检测的机制为了应对多线程环境。函数

public class Singleton {
    private static volatile Singleton INSTANCE = null;

    //私有化构造方法,防止被实例化
    private Singleton() {
    }

    //双重检测
    public static Singleton getInstance() {
        if (INSTANCE == null) { //①

            synchronized (Singleton.class) { //②

                if (INSTANCE == null) { //③
                    INSTANCE = new Singleton(); //④
                }
            }
        }

        return INSTANCE;
    }
}


须要注意的是,即便这种Doublecheck在C++中有效,但对JAVA5以前的代码仍是有一点问题的。优化

缘由在于,编译器会优化代码,可能致使赋值语句乱序执行,上述代码中,若是有2个线程A,B。A线程已经进入4位置,当4位置的代码执行时,须要注意 INSTANCE = new Singleton()这个行代码不是一个原子操做。线程

JVM可能在完成Singleton类的构造方法以前,会先把一块还未初始化完成的内存地址先分配给INSTANCE,而此时若是线程B进入1位置,会认为INSTANCE已经存在,从而返回了一个未初始化完成的内存块,这可能致使程序崩溃。正是因为是先给INSTANCE赋值在初始化内存块,仍是先初始化内存块再复制给INSTANCE,这个顺序没法保证,因此这种机制会出现问。因此在JAVA5以后,扩充了 volatile关键字,确保一个变量写入和读取操做的顺其不会被编译器优化成乱序,volatile变量也不会被缓存到cpu寄存器中,保证了其读取的一致性。接口

这和C#中的volatile的关键字是同样的做用。内存

除了上面2中常见的方法以外,还有其余方法。好比下面一种比较经典的实现方法,使用一个内部类,JVM自身保证了自身安全,这个模式也是在《Effective Java》这本书中推荐的,这种方式不依赖于java版本。

public class Singleton {
    private Singleton() {
    }

    public static final Singleton getInstance() {
        return InnerClass.INSTANCE;
    }

    private static class InnerClass {
        private static Singleton INSTANCE = new Singleton();
    }
}


但在JAVA5以后,最简单的单例实现方法是使用enum类型。

enum关键字是JAVA5中新增的,它和class,interface同样,也是一种数据类型。能够把它当作是一种特殊的类。能够在enum内部实现构造方法,字段,方法等,还能够实现接口。不过也有一些限定,枚举类中的构造器,默认为private修饰,且只能使用private。枚举类的全部实例必须在类中的第一行显式列出,不然这个枚举类不可能产生实例。JVM保证了这个每一个枚举值只被初始化一次。正是因为这样一个特色,咱们能够用以下代码能够实现单例。

public enum Singleton {INSTANCE;
    public void dosth(String arg) {
        // 逻辑代码
    }
}


上述单例中,Singleton.INSTANCE就表示了一个单例。很是简单高效。

注意Java中enum关键字和C#中的enum,差异很大,C#中不能使用这种方式。

相关文章
相关标签/搜索