Java单例实现及分析

双重检验锁实现方式

public class Singleton {
    //定义一个私有的空构造方法,防止直接用new实例化
    private Singleton() {}
    
    private static volatile Singleton singleton =  null;
    
    public static Singleton getInstance() {
        if(singleton == null) {
            synchronized(Singleton.class) {
                if(singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

双重校验锁,从代码的中可以看出,在同步代码块外多了一层instance为空的判断,由于单例对象只需要创建一次,如果后面再次调用getInstance()只需要直接返回单例对象即可,因此,在大部分情况下,调用getInstance()都不会执行到同步代码块,从而提高的程序性能。但是还需要考虑一种情况,假如两个线程A、B,线程A执行了if(instance == null)语句,它会任务单例对象没有创建,此时线程切到B也执行了同样的语句,B也认为单例对象没有创建,然后两个线程一次执行同步代码块,并分别创建了一个单例对象,所以为了解决这个问题,还需要在同步代码块中增加if(instance == null)的语句判断,避免重复创建的问题。

可以发现instance变量使用了volatile修饰,这样做的目的在于禁止指令重排序优化,所谓指令重排序优化是指在不改变原语义的前提下,通过调整指令的执行顺序让程序运行的更快,JVM中并没有规定编译器优化的相关内容,也就是说JVM可以自由的镜像指令重排序优化,这样会导致Singleton和将对象赋值给instance字段的顺序是不确定的。在摸个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化,若紧接着另一个线程来调用getInsatance,取到的就是状态不正确的对象,程序就会出错。volatile的一个语义是禁止指令重排序优化,也就是保证了instance遍历被赋值的时候对象已经是初始化过的,从而避免上面说的问题。

静态内部类实现方式

public class Singleton{
    private static class SingletonHolder{
        public static Singleton instance = new Singleton();
    }
    private Singleton(){}
    public static Singleton newInstance(){
        return SingletonHolder.instance;
    }
}

这种方式同样利用了类加载机制来保证只创建一个instance实例。它与饿汉模式一样,也是利用了类加载机制,因此不存在多线程并发的问题。不一样的是,它是在内部类里面去创建对象实例。这样的话,只要应用中不使用内部类,JVM就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载。也就是说这种方式可以同时保证延迟加载和线程安全。

原文地址:https://www.cnblogs.com/conswin/p/10411446.html