转自http://blog.csdn.net/huangyuan_xuan/article/details/52193006
通常情况下,我们写单例模式的时候无非就是三个步骤:构造器私有化,声明私有静态变量,提供静态获取实例的方法。简单说就是以下这种方式:
class SingletonA {
private static SingletonA instence = new SingletonA();
private SingletonA() {
}
public static SingletonA getInstance() {
return instence;
}
}
反射攻击和序列化攻击
是最基本的单例模式的写法,考虑到线程安全的问题,会用synchronized 关键字修饰getInstance()方法,另外还有饿汉式、懒汉式、静态内部类、双重校验锁的写法。
但是这些写法存在缺陷,可以利用反射的方式来实例化多个不同的实例,如下所示:
private static void testReflex() { try { SingletonA s1 = SingletonA.getInstance(); //正常获取 Class<SingletonA> cls = SingletonA.class; //反射获取 Constructor<SingletonA> constructor = cls .getDeclaredConstructor(new Class[] {});
constructor.setAccessible(true);//设置为true可以调用私有的构造方法
SingletonA s2 = constructor.newInstance(new Object[] {});
System.out.println(s1 == s2);//false
} catch (Exception e) { e.printStackTrace(); }
}
这种情况下,就会出现多个不同的实例,从而导致一些乱七八糟的结果。还有一种情况就是在序列化和反序列换的时候也会出现多个不同的实例,如下:
class SingletonB implements Serializable {
private static SingletonB instence = new SingletonB();
private SingletonB() {
}
public static SingletonB getInstance() {
return instence;
}
}
private static void testSingletonB() {
File file = new File("singleton");
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(file));
SingletonB SingletonB1 = SingletonB.getInstance();
oos.writeObject(SingletonB1);
oos.close(); //序列化写入的实例
ois = new ObjectInputStream(new FileInputStream(file));//序列化读回来的实例
SingletonB SingletonB2 = (SingletonB) ois.readObject();
System.out.println(SingletonB1 == SingletonB2);//false
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这种情况也会出现有多个实例的问题,这个问题可以在类中添加readResolve()方法来避免,即:
class SingletonB implements Serializable {
private static SingletonB instence = new SingletonB();
private SingletonB() {
}
public static SingletonB getInstance() {
return instence;
}
// 不添加该方法则会出现 反序列化时出现多个实例的问题
public Object readResolve() {
return instence;
}
}
枚举避免两种攻击
一个最简单的POJO类,如下:
enum SingletonC implements Serializable {
INSTANCE;
private String field;
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
}
测试方法:
private static void testEnum() {
File file = new File("singletonEnum");
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(file));
SingletonC singleton = SingletonC.INSTANCE;
oos.writeObject(SingletonC.INSTANCE);
oos.close();
ois = new ObjectInputStream(new FileInputStream(file));
SingletonC singleton2 = (SingletonC) ois.readObject();
System.out.println(singleton == singleton2);//true
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这种实现单例模式的方式是在 1.5之后才出现的,这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。
总结
1, 传统的单例模式的另外一个问题是一旦你实现了serializable接口,他们就不再是单例的了,因为readObject()方法总是返回一个 新的实例对象,就像java中的构造器一样。你可以使用readResolve()方法来避免这种情况,通过像下面的例子中这样用单例来替换新创建的实 例:
private Object readResolve(){
return INSTANCE;
}
而枚举类是可以默认实现序列化的,不会受到序列化攻击
2,枚举类反编译后是abstract修饰的,不能通过反射创建实例
public abstract class Singleton extends Enum {
private Singleton(String s, int i)
{
super(s, i);
}
protected abstract void read();
protected abstract void write();
public static Singleton[] values()
{
Singleton asingleton[];
int i;
Singleton asingleton1[];
System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
return asingleton1;
}
public static Singleton valueOf(String s)
{
return (Singleton)Enum.valueOf(singleton/Singleton, s);
}
Singleton(String s, int i, Singleton singleton)
{
this(s, i);
}
public static final Singleton INSTANCE;
private static final Singleton ENUM$VALUES[];
static
{
INSTANCE = new Singleton("INSTANCE", 0) {
protected void read()
{
System.out.println("read");
}
protected void write()
{
System.out.println("write");
}
};
ENUM$VALUES = (new Singleton[] {
INSTANCE
});
}
3,枚举单例其实也是一种饿汉式加载,在类加载的时候就创建了实例,所以天生线程安全