单例模式(饿汉式、懒汉式、静态内部类、枚举类)

一、饿汉式单例:

public class HungrySingleton {
    private HungrySingleton(){

    }
    private static HungrySingleton hungrySingleton = new HungrySingleton();
    public  static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

缺点:一开始就加载,可能会浪费空间

二、懒汉式单例

普通:

public class LazySingleton {
    private LazySingleton(){

    }
    private static LazySingleton lazySingleton;
    public static LazySingleton getInstacne(){
        if (lazySingleton==null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

这种模式下无法保证多线程的并发问题,会造成单例被破坏。

双重锁检测:

public class LazySingleton {
    private LazySingleton(){

    }
    private static LazySingleton lazySingleton;
    public static LazySingleton getInstacne(){
        if (lazySingleton == null){
            synchronized(LazySingleton.class){
                if (lazySingleton==null){
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
}

这种模式下,第一层lazySingleton判空是为了提高性能,同时保证是单例,只有初始为空的情况下才会继续执行下去,第二层lazySingleton判空是为了避免小概率事件的发生。在多线程的情况下,线程A和线程B同时通过第一层判空,进入到获得锁的入口,由于只有一个线程获得锁,假设线程A获得了锁,并创建了lazySingleton对象,当线程A执行完synchronize内部的代码释放锁,当线程B拿到锁后,就会执行第二层判空,就不会重复创建lazySingleton对象。如果没有第二层判空,线程B也会执行synchronize内部的代码,也就造成了创建2个lazySingleton对象。

但这种模式下,仍会出现问题。由于创建对象的过程并不是原子性操作的,在JVM层面来说,有3个执行指令,第一个是分配内存空间,第二个是执行构造方法,初始化对象,第三个是将对象指向空间,但由于CPU的指令优化,会导致指令执行顺序发生变化,可能存在执行顺序为132的情况。当线程A的指令执行顺序为132的情况,线程B获取lazySingleton时检测到lazySingleton不为null,就直接返回lazySingleton对象,但是lazySingleton对象还没有完成构造,所以线程B获取的lazySingleton为null。所以要保证lazySingleton对象必须加volatile。

volatile能保证可见性(当被volatile修饰的变量不会被缓存,一旦发生变化时就立即同步到主内存,不会出现线程A修改了变量,由于缓存机制的问题导致线程B无法看见修改后的变量值)和有序性(volatile有一个特性——禁止指令重排优化,这就保证了指令执行的顺序不会被改变),但无法保证原子性。

完整版双重检测锁(DCL懒汉):

public class LazySingleton {
    private LazySingleton(){

    }
    private  volatile static  LazySingleton lazySingleton;
    public static LazySingleton getInstacne(){
        if (lazySingleton == null){
            synchronized(LazySingleton.class){
                if (lazySingleton==null){
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
}

三、静态内部类

public class HolderSingleton {
    private HolderSingleton(){
        
    }
    public static HolderSingleton getInstance(){
        return innerClass.HOLDER_SINGLETON;
    }
    public static class innerClass{
        private static final HolderSingleton HOLDER_SINGLETON = new HolderSingleton();

    }
}

以上三种方式都会被反射所破坏。可以考虑在构造器加锁如:

private LazySingleton(){
    synchronized (LazySingleton.class){
        if (lazySingleton!=null){
            throw new RuntimeException("不要试图使用反射破坏单例");
        }
    }
}

但这种情况还是无法避免不创建实例的情况下直接利用反射获取构造器创建对象,所以引入了额外的标记作为判断:

    private static boolean mi = false;
    private LazySingleton(){
        synchronized (LazySingleton.class){
            if (mi == false){
                mi = true;
            }else {
                throw new RuntimeException("不要试图使用反射破坏单例");

            }
        }
    }

但这种情况如果被反编译的情况下获取到了标记名称,利用反射设置为false仍然无法防止破坏,所以引入了enum枚举作为最终手段,因为反射中的newInstance()方法中做了对枚举类的处理:

@CallerSensitive
public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        throw new IllegalArgumentException("Cannot reflectively create enum objects");//如果是枚举类就会抛出异常
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

四、枚举类单例

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

枚举内部的构造器不是无参构造器,而是有参构造器(String.class,Int.class),INSTANCE被public、static和final修饰。

反编译所得:

private EnumSingle (String s, int i) {
    super(s, i) ;
}
public EnumSingle getInstance (){
    return INSTANCE ;
}

public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[] ;
static{
        INSTANCE = new EnumSingle ("INSTANCE", 0) ;
        SVALUES = (new EnumSingle[] {
        INSTANCE
        });

猜你喜欢

转载自blog.csdn.net/qq_42500503/article/details/109078363
今日推荐