设计模式:一文搞定单例模式(防止反射反序列化clone破坏单例)Singleton Pattern-Java版

Posted 冲冲冲冲冲冲!!!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式:一文搞定单例模式(防止反射反序列化clone破坏单例)Singleton Pattern-Java版相关的知识,希望对你有一定的参考价值。

一、定义

单例模式,顾名思义,就是一个类从始至终只产生一个对象。现实生活中的例子有很多,比如在太阳系考虑问题,那么太阳和地球都可称为单例,再比如工具类,有时候没有把所有方法用static修饰(这不是个好办法),就应该把它做成单例,因为它没有不变的状态。

二、五种单例模式:饿汉式、懒汉式、双重检查锁、静态内部类、枚举

(一) 饿汉式,加载类时马上创建对象

/**
 * 饿汉式
 * 优点:线程安全、效率高
 * 缺点:不能做到“即用即创建”,有可能浪费内存资源
 */
public class Singleton {
	private static Singleton single=new Singleton();
	
	private Singleton() {}
	
	public static Singleton getInstance() {
		return single;
	}
}

(二) 懒汉式:用到时才创建对象,但线程不安全


/**
 * 懒汉式
 * 优点:即用即创建,用到时再创建
 * 缺点:线程不安全
 *
 */
class Singleton2 {
	private static Singleton2 single=null;
	
	private Singleton2() {}
	
	public static Singleton2 getInstance() {
		if(single==null) {
			single=new Singleton2();
		}
		return single;
	}
}

(三)双重检查锁:懒汉式的线程安全版


/**
 * 懒汉式-双重检查锁
 * 优点:即用即创建,用到时再创建
 * 缺点:效率一般
 */
class Singleton3 {
	private static Singleton3 single=null;
	
	private Singleton3() {}
	
	public static Singleton3 getInstance() {
		if(single==null) {
			synchronized(Singleton3.class) {
				if(single==null)
					single=new Singleton3();
			}
		}
		return single;
	}
}

(四)静态内部类:只有访问了getInstance方法,才会去加载静态内部类,达到“用到时才创建对象”的效果,线程安全,效率高

/**
 * 静态内部类(推荐)
 * 优点:线程安全,用到时才加载
 */
class Singleton4{
	private Singleton4() {}
	
	private static class SingletonHelper {
		final static Singleton4 instance=new Singleton4();
	}
	
	public static Singleton4 getInstance() {
		return SingletonHelper.instance;
	}
}

(五)枚举单例:《Effective Java》说枚举单例是最好的单例,有效防止反序列化

/**
 * 枚举(effective Java书中说枚举单例是最好的单例)(重磅推荐)
 *优点:线程安全、用到时再加载、避免反序列化、避免反射、避免克隆
 */
enum Singleton5 {
	INSTANCE
}

三、3个实验:使用反射、反序列化、clone来破坏单例

(一)反射破坏单例模式

public class Test {
	public static void test11() throws Exception {
		System.out.println("反射破坏饿汉式:");
		Class<?> c1 = Class.forName("chapter5.singleton.Singleton");
		Constructor<?> declaredConstructor = c1.getDeclaredConstructor();
		declaredConstructor.setAccessible(true);
		Singleton s1=Singleton.getInstance(),s2=Singleton.getInstance();
		Singleton s3=(Singleton) declaredConstructor.newInstance();
		System.out.println(s1);
		System.out.println(s2);
		System.out.println(s3);
	}
	
	public static void test12() throws Exception {
		System.out.println("反射破坏懒汉式:");
		Class<?> c1 = Class.forName("chapter5.singleton.Singleton2");
		Constructor<?> declaredConstructor = c1.getDeclaredConstructor();
		declaredConstructor.setAccessible(true);
		Singleton2 s1=Singleton2.getInstance(),s2=Singleton2.getInstance();
		Singleton2 s3=(Singleton2) declaredConstructor.newInstance();
		System.out.println(s1);
		System.out.println(s2);
		System.out.println(s3);
	}
	
	public static void test13() throws Exception {
		System.out.println("反射破坏双重检查锁:");
		Class<?> c1 = Class.forName("chapter5.singleton.Singleton3");
		Constructor<?> declaredConstructor = c1.getDeclaredConstructor();
		declaredConstructor.setAccessible(true);
		Singleton3 s1=Singleton3.getInstance(),s2=Singleton3.getInstance();
		Singleton3 s3=(Singleton3) declaredConstructor.newInstance();
		System.out.println(s1);
		System.out.println(s2);
		System.out.println(s3);
	}
	
	public static void test14() throws Exception {
		System.out.println("反射破坏静态内部类:");
		Class<?> c1 = Class.forName("chapter5.singleton.Singleton4");
		Constructor<?> declaredConstructor = c1.getDeclaredConstructor();
		declaredConstructor.setAccessible(true);
		Singleton4 s1=Singleton4.getInstance(),s2=Singleton4.getInstance();
		Singleton4 s3=(Singleton4) declaredConstructor.newInstance();
		System.out.println(s1);
		System.out.println(s2);
		System.out.println(s3);
	}
	
	public static void test15() throws Exception {
		System.out.println("反射破坏枚举类:");
		Class<?> c1 = Class.forName("chapter5.singleton.Singleton5");
		Constructor<?> declaredConstructor = c1.getDeclaredConstructor();
		declaredConstructor.setAccessible(true);
		Singleton5 s1=Singleton5.INSTANCE,s2=Singleton5.INSTANCE;
		Singleton5 s3=(Singleton5) declaredConstructor.newInstance();
		System.out.println(s1);
		System.out.println(s2);
		System.out.println(s3);
	}
	
	public static void main(String[] args) throws Exception {
		test11();
		test12();
		test13();
		test14();
		test15();
	}
}

输出结果:

反射破坏饿汉式:
chapter5.singleton.Singleton@15db9742
chapter5.singleton.Singleton@15db9742
chapter5.singleton.Singleton@6d06d69c
反射破坏懒汉式:
chapter5.singleton.Singleton2@7852e922
chapter5.singleton.Singleton2@7852e922
chapter5.singleton.Singleton2@4e25154f
反射破坏双重检查锁:
chapter5.singleton.Singleton3@70dea4e
chapter5.singleton.Singleton3@70dea4e
chapter5.singleton.Singleton3@5c647e05
反射破坏静态内部类:
chapter5.singleton.Singleton4@33909752
chapter5.singleton.Singleton4@33909752
chapter5.singleton.Singleton4@55f96302
反射破坏枚举类:
Exception in thread "main" java.lang.NoSuchMethodException: chapter5.singleton.Singleton5.<init>()
	at java.lang.Class.getConstructor0(Unknown Source)
	at java.lang.Class.getDeclaredConstructor(Unknown Source)
	at chapter5.singleton.Singleton.test15(Singleton.java:70)
	at chapter5.singleton.Singleton.main(Singleton.java:84)


可以看到,除了枚举,其它的单例模式都可以被反射破坏。
那么怎么防止反射呢?
方法也很简单,创建一个静态变量first,初始化为true,第一次创建时把它改为false,第二次以上再创建则抛出异常。以恶汉式为例(注意要把first放到single=new Singleton前面):

public class Singleton {
	
	private static boolean first=true;//必须放到new Singleton()前面,因为编译器从上到下加载
	private static Singleton single=new Singleton();
	
	private Singleton() {
		if(first) {
			synchronized(Singleton.class) {
				if(first) {
					first=false;
				}
			}
		} else {
			throw new RuntimeException("单例不能创建两个");
		}
	}
	
	public static Singleton getInstance() {
		return single;
	}
}

再次运行刚才的实验程序,运行结果:

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: 单例不能创建两个
	at chapter5.singleton.Singleton.<init>(Singleton.java:22)
	at chapter5.singleton.Singleton.<clinit>(Singleton.java:11)


(二)反序列化破坏单例模式
首先将所有的单例都实现Serializable 接口,生成一个serialVersionUID

public class Singleton implements Serializable {
	private static final long serialVersionUID = -6693877351780017099L;
	......
}

测试类:

public class TestSerializedRuin {
	
	public static void test(Object s1) {
		System.out.println("序列化前:"+s1);
		ObjectOutputStream oos=null;
		ObjectInput ois=null;
		//序列化
		try {
			oos=new ObjectOutputStream(new FileOutputStream("test.txt"));
			oos.writeObject(s1);
			oos.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				oos.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		//反序列化
		try {
			ois=new ObjectInputStream(new FileInputStream("test.txt"));
			System.out.println("序列化后::"+ ois.readObject());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				ois.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void test1() {
		System.out.println("反序列化破坏饿汉式:");
		Singleton s1=Singleton.getInstance();
		test(s1);
	}
	
	
	public static void test2() {
		System.out.println("反序列化破坏懒汉式:");
		Singleton2 s1=Singleton2.getInstance();
		test(s1);
	}
	
	public static void test3() {
		System.out.println("反序列化破坏双重检查锁:");
		Singleton3 s1=Singleton3.getInstance();
		test(s1);
	}
	
	public static void test4() {
		System.out.println("反序列化破坏静态内部类:");
		Singleton4 s1=Singleton4.getInstance();
		test(s1);
	}
	
	public static void test5() {
		System.out.println("反序列化破坏枚举:");
		Singleton5 s1=Singleton5.INSTANCE;
		test(s1);
	}
	
	public static void main(String[] args) {
		test1();
		test2();
		test3();
		test4();
		test5();
		
	}
}

输出结果:

反序列化破坏饿汉式:
序列化前:chapter5.singleton.Singleton@15db9742
序列化后::chapter5.singleton.Singleton@3b07d329
反序列化破坏懒汉式:
序列化前:chapter5.singleton.Singleton2@41629346
序列化后::chapter5.singleton.Singleton2@3d075dc0
反序列化破坏双重检查锁:
序列化前:chapter5.singleton.Singleton3@214c265e
序列化后::chapter5.singleton.Singleton3@3b9a45b3
反序列化破坏静态内部类:
序列化前:chapter5.singleton.Singleton4@7699a589
序列化后::chapter5.singleton.Singleton4@568db2f2
反序列化破坏枚举:
序列化前:INSTANCE
序列化后::INSTANCE

可以看出,只有枚举可以防止反序列化。其他的单例模式该如何防止反序列化呢?只需要在单例类中加入readResolve方法即可:

	private Object readResolve() {
		return single;
	}

这个readResolve方法只做了一个简单的事情,反序列化的时候,首先检查这个类有没有readResolve方法,若有,则返回readResolve指定的对象,若没有,才把反序列化的结果返回。
再次测试,查看结果:

反序列化破坏饿汉式:
序列化前:chapter5.singleton.Singleton@15db9742
序列化后::chapter5.singleton.Singleton@15db9742
反序列化破坏懒汉式:
序列化前:chapter5.singleton.Singleton2@3b07d329
序列化后::chapter5.singleton.Singleton2@3b07d329
反序列化破坏双重检查锁:
序列化前:chapter5.singleton.Singleton3@682a0b20
序列化后::chapter5.singleton.Singleton3@682a0b20
反序列化破坏静态内部类:
序列化前:chapter5.singleton.Singleton4@7cca494b
序列化后::单例模式防反射及性能

软件设计模式之单例模式

单例模式--反射--防止序列化破坏单例模式

GOF23—单例模式

设计模式 创建者模式 -- 单例模式存在的问题和解决办法(序列化反序列化破坏单例模式 & 反射破坏单例模式)

单例模式