【java】如何用正确用枚举实现线程安全的单例

2019-02-28  本文已影响0人  孙小猴猴猴

在java中类的加载和初始化过程都是线程安全的,枚举本身也是一个类,在反编译后可以看到枚举的成员使用的是static修饰符修饰,static成员随着类加载进内存之后而创建,所以对enum进行初始化的时候线程也是安全的。
当使用者尝试使用反射的方式创建一个枚举类型对象的时候,jvm会抛出java.lang.IllegalArgumentException: Cannot reflectively create enum objects的异常。所以使用enum创建单例可以最大程度地保证单例的唯一性和安全性。
看到了网上有一些利用枚举来设计单例的做法觉得并不是很好,因为这种方法创建单例,使用者可以通过反射来创建一个新的单例对象,这就破坏了单例的唯一性,代码如下:

public class SharedInstanceDemo {
    
    public static SharedInstanceDemo getInstanceDemo() {
        return singleTonTool.INSTANCE.getInsrtanceDemo();
    }
     // 私有化构造方法
    private SharedInstanceDemo() {}

    private static enum singleTonTool {
        INSTANCE;
        private SharedInstanceDemo instanceDemo;

        private singleTonTool() {
            instanceDemo = new SharedInstanceDemo();
        }

        public SharedInstanceDemo getInsrtanceDemo() {
            return instanceDemo;
        }

    }
}
public class JavaClass {

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
            IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {

        SharedInstanceDemo o1 = SharedInstanceDemo.getInstanceDemo();
        SharedInstanceDemo o2 = SharedInstanceDemo.getInstanceDemo();
        System.out.println(o1 == o2); // true
        Constructor<?> constructor = SharedInstanceDemo.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        System.out.println(o1 == constructor.newInstance());// false

    }
}

可以看到,如上设计单例,使用者依然可以通过反射创建一个新的单例变量。
正确的创建方式如下:

public enum SharedInstanceDemo{
    INSTANCE; // 这个就是单例的实例对象
    private String nameString;
    public void config(String name) {
        nameString = name;
    }
    public String getNameString() {
        return nameString;
    }
}

这种方式无法通过反射的方式创建一个新的单例变量,保证了单例对象的唯一性,如下:

public class JavaClass {

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
            IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
        // 枚举对象无法反射创建实例 IllegalArgumentException: Cannot reflectively create enum objects
        Constructor<?> constructor = SharedInstanceDemo.class.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        SharedInstanceDemo sharedInstanceDemo = (SharedInstanceDemo) constructor.newInstance();
    }
}

单例的使用:

public class JavaClass {

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
            IllegalAccessException, IllegalArgumentException, InvocationTargetException, IOException {
        // 只能通过INSTANCE获取单例对象
        SharedInstanceDemo demo = SharedInstanceDemo.INSTANCE;
        // 单例对象的方法调用
        SharedInstanceDemo.INSTANCE.config("哈哈哈");
        System.out.println(SharedInstanceDemo.INSTANCE.name());
        // 单例对象的成员变量赋值
        SharedInstanceDemo.INSTANCE.age = 5;
        System.out.println(SharedInstanceDemo.INSTANCE.age);
    }
}
上一篇下一篇

猜你喜欢

热点阅读