1.常见单例模式写法
/**
* 饿汉模式
*/
public class Singleton1 {
//私有静态不可变变量
private static Singleton1 SINGLETON1 = new Singleton1();
//私有构造函数
private Singleton1() {
System.out.println("Hello Singleton1");
}
//公共静态方法
public static Singleton1 getInstance() {
return SINGLETON1;
}
}
/**
* 懒汉模式
*/
public class Singleton2 {
//私有静态不可变变量
private static Singleton2 SINGLETON2;
//私有构造函数
private Singleton2() {
System.out.println("Hello Singleton2.");
}
//公共静态方法
public static Singleton2 getInstance() {
if (SINGLETON2 == null) {
SINGLETON2 = new Singleton2();
}
return SINGLETON2;
}
}
/**
* 双重检验锁模式
*/
public class Singleton3 {
// 私有静态不可变变量
private volatile static Singleton3 SINGLETON3;
// 私有构造函数
private Singleton3() {
System.out.println("Hello Singleton3.");
}
// 公共静态方法
public static Singleton3 getInstance() {
if (SINGLETON3 == null) {
synchronized (Singleton3.class) {
if (SINGLETON3 == null) {
SINGLETON3 = new Singleton3();
}
}
}
return SINGLETON3;
}
}
/**
* 内部静态类实现单例模式
*/
public class Singleton4 {
private static class SingletonHolder{
private static final Singleton4 SINGLETON4 = new Singleton4();
}
private Singleton4() {
System.out.println("Hello Singleton4.");
}
public static Singleton4 getInstance() {
return SingletonHolder.SINGLETON4;
}
}
/**
* 枚举类实现
*/
public enum Singleton5 {
INSTANCE;
public void sayHello() {
System.out.println("Hello Singleton5.");
}
}
单例模式的特点:私有化的构造函数;私有的静态的全局变量;公有的静态的方法。
2.单例模式的问题
-
通过反射机制可生成新的对象,破坏实例唯一性
在《Effective Java》最佳实践第3条中提示:“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。” -
单例类如果实现Serializable接口后可通过序列化与反序列化的方式生成新的对象,破坏实例唯一性
在《Effective Java》最佳实践第77条中提示:“单例类如果实现了Serializable接口后它就不再是一个Singleton了。因为任何一个readObject方法,不管是显示的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。readResovle特性允许你用readObject创建的实例代替另一个实例。如果Singleton类要实现Serializable接口,则下面的readResovle方法可以满足它的Singleton特性。”
// readResovle for instance control
private Object readResovle() {
return INSTANCE;
}
该方法忽略了被序列化的对象,只返回该类初始化时创建的那个特殊的Singleton实例INSTANCE。如果依赖readResovle进行实例控制,带有对象引用类型的所有实例域则都必须声明为transient的。否则,就有可能在readResovle方法被运行之前,保护指向反序列化对象的引用。
3.单例模式最佳实践
// 单例模式
public enum Singleton {
INSTANCE;
}
4.从底层原理看懂枚举
public final class Singleton extends java.lang.Enum<Singleton> {
public static final Singleton INSTANCE;
public static Singleton [] values();
public static Singleton valueOf(java.lang.String);
static {};
}
上述代码为javap反编译得到的信息。从上述信息可知:
- 编译器将枚举enum类型编译为final(不可变)类型的class类;
- 编译器对所有的枚举成员处理成public static final的枚举常量,并在静态域中进行初始化;
- 编译之后增加一个静态块,在此静态块中创建一个对象并将此对象赋值给静态对象;
- 编译器重新定义了构造器,为每个构造器都增加了两个参数和添加父类构造方法的调用;
- 编译器为枚举类添加了 values() 和 valueOf()方法。values()方法返回一个枚举类型的数组,可用于遍历枚举类型。valueOf()方法返回一个枚举类型,可用于字符串转换成枚举类型。
4.枚举enum是实现单例模式的最佳实践
- 编译器重新定义了构造器------防止反射攻击
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum Singleton {
INSTANCE;
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Singleton singleton1 = Singleton.INSTANCE;
Singleton singleton2 = Singleton.INSTANCE;
System.out.println("正常情况下,实例化两个实例,判断它们是否相等:" + (singleton1 == singleton2));
// constructor中没有无参构造器只有一个参数为(String.class,int.class)构造器
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton singleton3 = constructor.newInstance();
System.out.println("通过反射攻击单例模式情况下,实例化两个实例,判断它们是否相等:" + (singleton1 == singleton3));
}
}
输出结果如下:
正常情况下,实例化两个实例,判断它们是否相等:true
Exception in thread "main" java.lang.NoSuchMethodException: com.nwpu.davince.enumeration.Singleton.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at com.nwpu.davince.enumeration.Singleton.main(Singleton.java:17)
让我们看看到底哪里出了问题?通过debug模式查看 Singleton.class.getDeclaredConstructor()方法得知,返回结果没有无参构造函数只有一个(String name, int ordinal)类型的构造函数,我们看一下Enum的源码就会明白,编译器重新定义的构造器继承自Enum枚举抽象类。
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
public final String name() {
return name;
}
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
// 省略其余代码
}
讲道理,刚才抛出的异常是因为Singleton枚举拿不到无参构造函数,但Singleton可以拿到父类Enum的构造函数,我们可以来试试看,是否是真的因为没有构造函数导致异常抛出来的。
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum Singleton {
INSTANCE;
public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Singleton singleton1 = Singleton.INSTANCE;
Singleton singleton2 = Singleton.INSTANCE;
System.out.println("正常情况下,实例化两个实例,判断它们是否相等:" + (singleton1 == singleton2));
// constructor中没有我们无参构造器只有一个参数为(String.class,int.class)构造器
// Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
// 获取父类Enum的构造函数
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
Singleton singleton3 = constructor.newInstance();
System.out.println("通过反射攻击单例模式情况下,实例化两个实例,判断它们是否相等:" + (singleton1 == singleton3));
}
}
输出结果如下:
正常情况下,实例化两个实例,判断它们是否相等:true
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.nwpu.davince.enumeration.Singleton.main(Singleton.java:22)
我们来看看at java.lang.reflect.Constructor.newInstance(Constructor.java:417)这行错误抛出的源代码:
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
// 反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
- 避免序列化问题
Talk is cheap,show me code!
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public enum Singleton implements Serializable{
INSTANCE;
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
private Singleton() {
}
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
Singleton singleton = Singleton.INSTANCE;
singleton.setMessage("单例枚举序列化");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.obj"));
oos.writeObject(singleton);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton singleton2 = (Singleton) ois.readObject();
ois.close();
System.out.println("序列化出来后的信息为:" + singleton2.getMessage());
System.out.println("序列化前后的两个对象是否相等:" + (singleton == singleton2));
}
}
输出结果为:
序列化出来后的信息为:单例枚举序列化
序列化前后的两个对象是否相等:true
原创不易,如需转载,请注明出处@author Davince!