饿汉单例模式一定要加final?

今天有同学问我,饿汉单例模式为什么一定要加final关键字?即便使用多个线程去访问,加了final关键词和不加效果都是一样的呀。那么可不可以不加final,只用static呢?(如下写法)

public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton() {
    }

    public static Singleton getSingle() {
        System.out.println(singleton);
        return singleton;
    }
}

答案是 不可以!必须加上final关键词!
由于在网上查询,大多数博客对这个问题没有明确说明,有的甚至说的还是错的,所以特来实证这个问题。

首先你要知道的是,反射可以随时随地脱下JVM的底裤。。。所以Java中的任何权限控制,在反射环境下,基本是不存在的。

大概描述下思路:
1.我先用反射调用单例类的构造函数,创建出新的单例对象来。
2.使用field去访问到原本的单例对象。
3.使用set方法把新创建的单例对象赋值给原本的对象。
4.查看是否能赋值成功。
基于不加final的后果如下程序可证:

public class Main {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        System.out.println("第一次拿到单例模式创建的对象: " + Singleton.getSingle());

        Class clazz = Singleton.class;
        Constructor c0 = clazz.getDeclaredConstructor();
        c0.setAccessible(true);
        Singleton po = c0.newInstance();
        System.out.println("反射创建出来的对象: " + po);

        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Singleton singleton1 = (Singleton) field.get(Singleton.getSingle());
            System.out.println("拿到单例模式创建的对象: " + singleton1);
            field.set(Singleton.getSingle(), po); //把反射创建出来的对象赋值给单例对象
            System.out.println("第二次拿到单例模式创建的对象: " + Singleton.getSingle());
        }
    }
}

----------------------------------------------------
运行结果:
第一次拿到单例模式创建的对象: com.service.Singleton@16b98e56
反射创建出来的对象: com.service.Singleton@7ef20235
拿到单例模式创建的对象: com.service.Singleton@16b98e56
第二次拿到单例模式创建的对象: com.service.Singleton@7ef20235

发现了吧,它的地址变了,不该改变的实例,改变了!

如果加上final运行结果为:

第一次拿到单例模式创建的对象: com.service.Singleton@16b98e56
反射创建出来的对象: com.service.Singleton@7ef20235
拿到单例模式创建的对象: com.service.Singleton@16b98e56
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final com.service.Singleton field com.service.Singleton.singleton to com.realife.service.Singleton
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
    at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
    at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
    at java.lang.reflect.Field.set(Field.java:764)
    at com.service.Main.main(Main.java:23)

所以这样看来,加上final是更安全的单例方式。

除此之外,网上还有一种说法,为了保证只能创建一个实例,杜绝反射通过构造函数作恶,可以使用以下方式:

private static volatile boolean flag = false;
    
    private Singleton(){
        synchronized (Singleton.class) {
            if(!flag){
                flag = true;
            }else{
                throw new RuntimeException("单例只能创建一个");
            }
        }
    }

你可能感兴趣的:(饿汉单例模式一定要加final?)