单例模式

设计模式之单例模式

单例模式:即类对象在全局只有一个实例。

饿汉式单例模式

在类加载的时候,就创建类的实例,这个是线程安全的单例模式。

public class SingleTon1 {
	// 私有构造方法
	private SingleTon1() {

	}
	// 类加载时创建实例对象
	private static SingleTon1 instance = new SingleTon1();
	// 获取实例对象的方法,不需要synchronize关键字,因为本身就是线程安全的
	public static SingleTon1 getInstance() {
		return instance;
	}
}

懒汉式单例模式

在需要使用类实例的时候,再去创建这个类的实例,在高并发情况下可能会出现多个实例的情况,避免方法,可以在获取实例的方法上使用synchronize关键字进行检查。但是对性能是有损失的。

public class SingleTon2 {
	// 私有构造方法
	private SingleTon2() {

	}
	// 类加载时创建实例对象
	private static SingleTon2 instance = null;
	// 获取实例对象的方法,用synchronize关键字保证线程安全
	public static synchronized SingleTon2 getInstance() {
		if(null == instance){
			instance = new SingleTon2();
		}
		return instance;
	}
}

双重检查锁单例模式

也是延时加载,在需要时创建这个类的实例,在获取实例的方法内部进行双重检查锁,来保证线程安全。但是根据jvm内部的优化,可能会导致创建多个实例。

public class SingleTon3 {
	// 私有构造方法
	private SingleTon3() {

	}
	// 类加载时创建实例对象
	private static SingleTon3 instance = null;
	// 获取实例对象的方法,用synchronize关键字保证线程安全
	public static SingleTon3 getInstance() {
		if(null == instance){
			 synchronized (SingleTon3.class) {
				if(null == instance){
					instance = new SingleTon3();
				}
			}
		}
		return instance;
	}
}

静态内部类单例模式

使用静态内部类的方式创建类的实例,可以实现延时加载,而且是线程安全的。

public class SingleTon4 {
	// 私有构造方法
	private SingleTon4() {

	}

	// 使用静态内部类
	private static final class SingTonInner {
		private static final SingleTon4 INSTANCE = new SingleTon4();
	}

	// 获取实例对象的方法
	public static SingleTon4 getInstance() {
		return SingTonInner.INSTANCE;
	}
}

枚举类单例模式

线程安全的方式创建类的实例,在类加载时创建。

public enum SingleTon5 {
	INSTANCE;// 实例
	//添加方法
	public void say() {

	}
}

上述的5种模式,都要求私有化构造方法。

利用反射区破解单例模式(不含枚举)

利用java的反射机制去获取类的实例

public class Test1 {
	public static void main(String[] args) throws Exception {
		SingleTon1 s1 = SingleTon1.getInstance();
		SingleTon1 s2 = SingleTon1.getInstance();
		System.out.println(s1 == s2);
		// 获取类对象
		Class<SingleTon1> clazz = (Class<SingleTon1>) Class.forName("com.pattern.singleton.SingleTon1");
		// 获取构造方法
		Constructor<SingleTon1> constructor = clazz.getDeclaredConstructor();
		constructor.setAccessible(true);
		SingleTon1 s3 = constructor.newInstance();
		System.out.println(s1 == s3);
	}
}

测试结果

true
false

如何防止反射破解

防止也很简单,在构造方法内部判断实例是否存在,存在的话抛异常就可以了。
修改后的SingleTon1.java:

public class SingleTon1 {
	// 私有构造方法
	private SingleTon1() {
		if(null != instance){
			throw new RuntimeException();
		}
	}
	// 类加载时创建实例对象
	private static SingleTon1 instance = new SingleTon1();
	// 获取实例对象的方法,不需要synchronize关键字,因为本身就是线程安全的
	public static SingleTon1 getInstance() {
		return instance;
	}
}

修改后的测试结果:

true
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.pattern.singleton.Test1.main(Test1.java:15)
Caused by: java.lang.RuntimeException
	at com.pattern.singleton.SingleTon1.<init>(SingleTon1.java:7)
	... 5 more

利用序列化和反序列化破解单例模式

要实现序列化和反序列化,必须要类实现Serializable接口。
测试代码:

public class Test2 {
	public static void main(String[] args) throws Exception {
		SingleTon1 s1 = SingleTon1.getInstance();
		SingleTon1 s2 = SingleTon1.getInstance();
		System.out.println(s1 == s2);
		//序列化
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("a.txt"));
		oos.writeObject(s1);
		oos.flush();
		oos.close();
		//反序列化
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("a.txt"));
		SingleTon1 s3 = (SingleTon1) ois.readObject();
		ois.close();
		System.out.println(s1 == s3);
	}
}

测试结果:

true
false

如何防止序列化和反序列化破解

自己写一个方法,readResolve方法,空参,返回Object
修改后的SingleTon1.java:

public class SingleTon1 implements Serializable{
	// 私有构造方法
	private SingleTon1() {
		if(null != instance){
			throw new RuntimeException();
		}
	}
	// 类加载时创建实例对象
	private static SingleTon1 instance = new SingleTon1();
	// 获取实例对象的方法,不需要synchronize关键字,因为本身就是线程安全的
	public static SingleTon1 getInstance() {
		return instance;
	}
	
	private Object readResolve(){
		return instance;
	}
}

测试结果:

true
true
posted @ 2018-08-23 16:32  真菜啊  阅读(105)  评论(0编辑  收藏  举报