美文网首页
【java】如何用正确用枚举实现线程安全的单例

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

作者: 孙小猴猴猴 | 来源:发表于2019-02-28 22:09 被阅读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);
        }
    }
    

    相关文章

      网友评论

          本文标题:【java】如何用正确用枚举实现线程安全的单例

          本文链接:https://www.haomeiwen.com/subject/bxrluqtx.html