设计模式:一文搞定单例模式(防止反射、反序列化、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;
	}
}

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


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

说明:volatile关键字修饰是很有必要的。single=new Singleton3();分为三步:

  1. 分配内存
  2. 初始化
  3. 将对象地址赋给引用变量
    一般而言,在单线程情况下是没问题的。但在多线程环境下可能有问题了,处理器可能会优化为1->3->2。
    线程1新建完对象后,执行了1->3,还没有初始化(步骤2),线程2判断single==null,发现已经有对象了,直接访问single引用,但这时候的single指向的对象没有初始化!
    使用volatile的好处就是可以禁止指令重排序,也就是顺序一定是1->2->3。

(四)静态内部类:只有访问了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
序列化后::chapter5.singleton.Singleton4@7cca494b
反序列化破坏枚举:
序列化前:INSTANCE
序列化后::INSTANCE

(三)克隆破坏单例模式:
首先将所有类实现Cloneable接口,并生成clone方法:

public class Singleton implements Serializable,Cloneable {
......
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

枚举类型可以实现Cloneable接口,但是不能实现clone方法,编译器自动报错:

Cannot override the final method from Enum<Singleton5>

估计枚举在设计的时候就考虑了这一点,把clone方法定义为final了。
测试类:

public class CloneRuin {
	public static void main(String[] args) throws CloneNotSupportedException {
		System.out.println("克隆破坏饿汉式");
		Singleton s=Singleton.getInstance();
		Singleton clone = (Singleton) s.clone();
		System.out.println(s);
		System.out.println(clone);
		
		System.out.println("克隆破坏懒汉式");
		Singleton2 s1=Singleton2.getInstance();
		Singleton2 clone1 = (Singleton2) s1.clone();
		System.out.println(s1);
		System.out.println(clone1);
		
		System.out.println("克隆破坏双重检查锁");
		Singleton3 s2=Singleton3.getInstance();
		Singleton3 clone2 = (Singleton3) s2.clone();
		System.out.println(s2);
		System.out.println(clone2);
		
		System.out.println("克隆破坏双重检查锁");
		Singleton4 s3=Singleton4.getInstance();
		Singleton4 clone3 = (Singleton4) s3.clone();
		System.out.println(s3);
		System.out.println(clone3);
	}
}

输出结果:

克隆破坏饿汉式
chapter5.singleton.Singleton@15db9742
chapter5.singleton.Singleton@6d06d69c
克隆破坏懒汉式
chapter5.singleton.Singleton2@7852e922
chapter5.singleton.Singleton2@4e25154f
克隆破坏双重检查锁
chapter5.singleton.Singleton3@70dea4e
chapter5.singleton.Singleton3@5c647e05
克隆破坏双重检查锁
chapter5.singleton.Singleton4@33909752
chapter5.singleton.Singleton4@55f96302

怎么保证单例不被克隆破坏呢?和反序列化的解决方案类似,直接在clone()方法中修改返回值为single即可。

	@Override
	protected Object clone() throws CloneNotSupportedException {
		return single;
	}

修改后的测试结果:

克隆破坏饿汉式
chapter5.singleton.Singleton@15db9742
chapter5.singleton.Singleton@15db9742
克隆破坏懒汉式
chapter5.singleton.Singleton2@6d06d69c
chapter5.singleton.Singleton2@6d06d69c
克隆破坏双重检查锁
chapter5.singleton.Singleton3@7852e922
chapter5.singleton.Singleton3@7852e922
克隆破坏双重检查锁
chapter5.singleton.Singleton4@4e25154f
chapter5.singleton.Singleton4@4e25154f

四、总结

小小的单例模式,也可以引申出如此多的问题,有了问题,才会去寻找解决方案。单例模式是简单的一种设计模式,但深刻透彻理解它,还是需要思考怎么避免问题的发生。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学无止境jl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值