Effective Java 第3条 遇用私有构造或者枚举类强化Singleton属性

用私有构造或者枚举类强化Singleton属性

Singleton(单例)是指仅仅被实例化一次的类,Singleton通常表示一个无状态的对象,或者本质上唯一的系统组件。

实现Singleton有两种常用的方式,这两种方式都要保证构造器的私有化,并导出共有的静态成员变量,以便客户端能够获取类唯一的实例,第一种方法,饿汉模式:

public class SingletonDemo{
    
    
    public static final SingletonDemo SINGLETON_DEMO = new SingletonDemo();
    private SingletonDemo(){
    
    }
}

这种方式私有构造器仅被调用一次,用来实例化公有的静态域SINGLETON_DEMO,由于缺少公用的,或者受保护的构造器,所以保证了SINGLETON_DEMO的全局唯一性,一旦SINGLETON_DEMO被实例化,只会存在一个SINGLETON_DEMO实例。但是可以通过借助反射的方式,调用私有构造器。举个例子:

public class Main{
    
    
    public static void main(String[] args){
    
    
        SingletonDemo singletonDemo = SingletonDemo.SINGLETON_DEMO;
        SingletonDemo singletonDemo2 = SingletonDemo.SINGLETON_DEMO;
        Class clazz = SingletonDemo.class;
        SingletonDemo newSingletonDemo = null;
        try {
    
    
            Constructor constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            newSingletonDemo = (SingletonDemo) constructor.newInstance();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        System.out.println(singletonDemo == singletonDemo2);
        System.out.println(singletonDemo == newSingletonDemo);
        System.out.println(singletonDemo2 == newSingletonDemo);
    }
}

如果需要抵御这种攻击,可以在构造器中添加如下代码,使其在第二次调用私有构造器的时候抛出异常:

public class SingletonDemo{
    
    
    public static final SingletonDemo SINGLETON_DEMO = new SingletonDemo();
    private SingletonDemo(){
    
    
       if(SINGLETON_DEMO != null){
    
    
            throw new AssertionError();
        }
    }
}

实现Singleton的第二中方法时,共有的成员是一个静态方法,懒汉模式:

public class SingletonDemo{
    
    
    private static final SingletonDemo SINGLETON_DEMO = new SingletonDemo();

    private SingletonDemo() {
    
    
        if(SINGLETON_DEMO != null){
    
    
            throw new AssertionError();
        }
    }

    public static SingletonDemo getInstance() {
    
    
        return SINGLETON_DEMO;
    }
}

对于静态方法getInstance()的所有调用,都会返回同一个对象的实例,所以,永远不会创建其他对象的实例。


对比

饿汉模式:(public-field)公有域的优势在于,可以很清楚的表示这是一个Singleton类,第二个优势在于,它更简单。
懒汉模式:静态工厂方法的优势之一在于,提供了灵活性,在不改变API情况下,可以任意改变该类是否为Singleton的想法,也可以很容易的修改为为每个调用该方法的线程返回一个唯一的实例。第二优势是,如果程序需要泛型单例工厂,此模式可以很好做到。

除非满足"懒汉模式"以上任意一个优势,否则还是有限考虑公有域的方法。

序列化和反序列化对单例的攻击

如果要将Singleton类变成可序列化的,仅仅在类上声明 implements Serializable是不够的,因为每次反序列化一个对象的时候,readObject()方法都会创建一个新的实例,所以为了保证Singleton的单一性,必须声明所有实例域都是瞬时(transient),并在类中添加一个readResolve方法(不是重写也不是实现,会在后续文章说明),反序列化之后,新建对象上的readResolve()方法就会调用,然后,该方法返回的对象引用 将被返回,取代新的对象。举个栗子:

public class SingletonDemo implements Serializable {
    
    
    private static final SingletonDemo SINGLETON_DEMO = new SingletonDemo();
    private transient String name;
    private transient Integer age;
    private SingletonDemo() {
    
    
        if (SINGLETON_DEMO != null) {
    
    
            throw new AssertionError();
        }
    }
    public static SingletonDemo getInstance() {
    
    
        return SINGLETON_DEMO;
    }
    // 声明readResolve()方法
    private Object readResolve() {
    
    
        System.out.println("readResolve");
        return SINGLETON_DEMO;
    }
}

main:

public class Main{
    
    
    public static void main(String[] args) throws Exception {
    
    
        ByteArrayOutputStream by = new ByteArrayOutputStream();

        SingletonDemo2 s1 = SingletonDemo2.getInstance();

        ObjectOutputStream out = new ObjectOutputStream(by);
        out.writeObject(s1);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(by.toByteArray()));
        SingletonDemo2 s2 = (SingletonDemo2) in.readObject();
        in.close();

        System.out.println(s1 == s2);

    }
}

单元素枚举类型实现Singleton

单例类,如下:

enum SingletonC {
    
    
	INSTANCE;
	private String field;

	public String getField() {
    
    
		return field;
	}

	public void setField(String field) {
    
    
		this.field = field;
	}

}

main:

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

这种方法在功能上与公有域方法相近,但是它更加简洁,无偿提供了序列化机制,绝对防止多次实例化,即使是在面对复杂序列化或者反射攻击的时候。虽然这种方法还没有广泛采用,但是单元素的枚举类型已经成为实现Singleton的最佳方法。 ----《Effective Java 中文版 第三版》


参考文章:https://www.jianshu.com/p/e4943e78024d https://blog.csdn.net/huangyuan_xuan/article/details/52193006

猜你喜欢

转载自blog.csdn.net/qq_38941937/article/details/115103251