【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);
}
}