12、彻底玩转单例模式

饿汉式单例:

//饿汉式单例(造成内存资源浪费)
public class HungrySingle {

    private HungrySingle(){

    }

    private final static HungrySingle HUNGRY_SINGLE = new HungrySingle();

    public static HungrySingle getInstance(){
        return HUNGRY_SINGLE;
    }

}

懒汉式单例:

//懒汉式单例(用的时候才去实例化)
public class LazySingle {

    private LazySingle(){

    }
    private static LazySingle lazySingle;

    public static LazySingle getInstance(){
        if (lazySingle==null){
            lazySingle = new LazySingle();
        }
        return lazySingle;
    }
}

问题:

懒汉式在多线程下:

public class LazySingle {

    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"  OK");
    }
    private static LazySingle lazySingle;

    public static LazySingle getInstance(){
        if (lazySingle==null){
            lazySingle = new LazySingle();
        }
        return lazySingle;
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                LazySingle.getInstance();
            }).start();
        }
    }
}

结果:(可能会出现多次创建对象——即和单例相违背)

Thread-0  OK
Thread-1  OK

双重检测锁模式的懒汉式单例(DCL懒汉式):

public class LazySingle {

    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"  OK");
    }
    private static LazySingle lazySingle;

    //双重检测锁模式的懒汉式单例,简称DCL懒汉式
    public static LazySingle getInstance(){
        if (lazySingle==null){
            synchronized (LazySingle.class){
                if (lazySingle==null){
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                LazySingle.getInstance();
            }).start();
        }
    }
}

结果:(可以发现在这种情况下单例能够保证)

Thread-0  OK

但是,上面的双重检测锁也可能出现问题:
因为new LazySingle();不是一个原子操作
new操作会经过三步:
1、分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
以上三步期望的执行是1、2、3。但是如果发生指令重排,可能编程1、3、2,这就会导致发生错误。
于是在LazySingle前加上volatile

public class LazySingle {

    private LazySingle(){
        System.out.println(Thread.currentThread().getName()+"  OK");
    }

    private volatile static LazySingle lazySingle;

    //双重检测锁模式的懒汉式单例,简称DCL懒汉式
    public static LazySingle getInstance(){
        if (lazySingle==null){
            synchronized (LazySingle.class){
                if (lazySingle==null){
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                LazySingle.getInstance();
            }).start();
        }
    }
}

静态内部类实现的单例

//静态内部类实现的单例
public class InnerSingle {

    private InnerSingle(){

    }

    public static InnerSingle getInstance(){
        return InnerClass.INNER_SINGLE;
    }

    public static class InnerClass{
        private static final InnerSingle INNER_SINGLE = new InnerSingle();
    }

}

但是通过反射技术可以破解以上三种代码

利用反射破坏双重检测锁模式的懒汉式单例

public class TestReflect {

    public static void main(String[] args) throws Exception {
        LazySingle instance1 = LazySingle.getInstance();

        //通过反射创建对象
        Constructor declaredConstructor = LazySingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//暴力反射,强制访问私有构造方法
        LazySingle instance2 = declaredConstructor.newInstance();

        System.out.println(instance1);
        System.out.println(instance2);

    }
}

结果:(可以发现,结果的两个对象不是同一个对象,说明反射对单例模式进行了破坏)

main  OK
main  OK
com.company.single.LazySingle@1b6d3586
com.company.single.LazySingle@4554617c

如何避免通过反射进行破坏:

通过三重检测(即通过标志位在私有构造方法中加上标志判断)

public class LazySingle {

    //利用标志位来进行判断,需要对关键字flag进行加密处理
    private static boolean flag = false;
    private LazySingle(){

        synchronized (LazySingle.class){
            if (flag==false){
                flag = true;
            }else{
                throw new RuntimeException("不要试图通过反射破坏单例的异常");
            }
        }

    }

    private volatile static LazySingle lazySingle;

    //双重检测锁模式的懒汉式单例,简称DCL懒汉式
    public static LazySingle getInstance(){
        if (lazySingle==null){
            synchronized (LazySingle.class){
                if (lazySingle==null){
                    lazySingle = new LazySingle();
                }
            }
        }
        return lazySingle;
    }
}

结果:

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.company.single.TestReflect.main(TestReflect.java:14)
Caused by: java.lang.RuntimeException: 不要试图通过反射破坏单例的异常
    at com.company.single.LazySingle.(LazySingle.java:13)
    ... 5 more

但是还是可以用反编译获取flag变量名来进行破坏:

public static void main(String[] args) throws Exception {
        //
        Field flag = LazySingle.class.getDeclaredField("flag");
        flag.setAccessible(true);

        //通过反射创建对象
        Constructor declaredConstructor = LazySingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);//暴力反射,强制访问私有构造方法
        LazySingle instance2 = declaredConstructor.newInstance();
        flag.set(instance2,false);
        LazySingle instance3 = declaredConstructor.newInstance();

        System.out.println(instance2);
        System.out.println(instance3);
    }

结果:(可以发现反射又破坏了单例)

com.company.single.LazySingle@74a14482
com.company.single.LazySingle@1540e19d

你可能感兴趣的:(12、彻底玩转单例模式)