1、编码实现的单例
- 静态内部类方式(JVM类加载实现)
public class Singleton {
private Singleton() {}
private static class SingleTonHolder {
static final Singleton INSTANCE = new Singleton();
}
// 调用 getInstance 方法,才获取加载 SingleTonHolder,JVM保证只会有一个线程对类进行加载
public static Singleton getInstance() {
return SingleTonHolder.INSTANCE;
}
}
- java关键字实现(JMM实现)
public class SingleTon {
private Singleton() {}
// 利用 volatile 的语义:禁止指令重排序
private volatile static SingleTon INSTANCE;
public static SingleTon get() {
// Double-Check:即保证并发量,又保证线程安全。
if (INSTANCE == null) {
synchronized (SingleTon.class) {
if (INSTANCE == null) {
INSTANCE = new SingleTon ();
}
}
}
return INSTANCE ;
}
}
这两个手写编码的单例看起来很完美,但是利用反射、序列化,可以获得新对象,破坏单例。
2、反射问题
- 反射破坏
public class SingletonReflect {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Singleton origin = Singleton.getInstance();
System.out.println("origin: " + System.identityHashCode(origin));
Class<Singleton> singletonClass = Singleton.class;
Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton reflect = constructor.newInstance();
System.out.println("reflect: " + System.identityHashCode(reflect));
}
}
执行结果:
origin: 59559151
reflect: 1450821318
两个对象的地址值并不相同。反射破坏成功。
-
解决方法思考
// -
解决方法
在构造函数中进行判断,实例是否不为 null,如果不为null,抛出异常。
public class Singleton {
private Singleton() {
// 增加判断
if (SingleTonHolder.INSTANCE != null) {
throw new IllegalStateException("can't reflect Singleton");
}
}
private static class SingleTonHolder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingleTonHolder.INSTANCE;
}
}
这里判断的依据涉及到的知识点:
1、初始化一个类,包括执行这个类的静态初始化和初始化在这个类中声明的静态字段。因此静态成员变量会随着类加载而存在。
2、Java规范中一个类或接口类型T被初始化的情况包括:
2.1:T是一个类,而且一个T类型的实例被创建;
2.2:T是一个类,且T中声明的一个静态方法被调用;
2.3:T中声明的一个静态字段被赋值;
2.4:T中声明的一个静态字段被使用,而且这个字段不是一个常量字段;常量字段:使用关键字 final 修饰的字段。
2.5:T是一个顶级类(top level class,见java语言规范的§7.6),而且一个断言语句嵌套在T内部被执行。
运行结果:
3、序列化问题
实现序列化,需要单例类实现Serializable
接口,定义serialVersionUID
常量。
- 序列化破坏
public class SingletonSerialize {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Singleton origin = Singleton.getInstance();
System.out.println("origin: " + System.identityHashCode(origin));
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\serialize\\instance.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\serialize\\instance.txt"))) {
oos.writeObject(origin);
Singleton serialize = (Singleton) ois.readObject();
System.out.println("serialize: " + System.identityHashCode(serialize));
}
}
}
执行结果:
origin: 1476011703
serialize: 1911728085
- 解决方法思考
// - 解决方法
在单例类中增加两个方法,自定义反序列化方法。当反序列化时,抛出异常。
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize singleton");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize singleton");
}
运行结果:
4、总结
一个编码实现的完美单例写法:
public class Singleton implements Serializable {
private static final long serialVersionUID = -3782723772939643172L;
private static class SingleTonHolder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingleTonHolder.INSTANCE;
}
private Singleton() {
if (SingleTonHolder.INSTANCE != null) {
throw new IllegalStateException("can't reflect singleton");
}
}
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize singleton");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize singleton");
}
}