JAVA三种实现单例模式方法(一):懒汉式实现单例设计模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jingzi123456789/article/details/79978946
package instance.lazy;

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 懒汉式单例
 * @author jingzi
 * 单例模式是最常用的设计模式,一个完美的单例需要做到哪些事呢?
 * 1、单例
 * 2、延迟加载
 * 3、线程安全
 * 4、没有性能问题
 * 5、防止序列化产生新对象
 * 6、防止反射攻击
 */
public class LazyType implements Serializable {
	
	private static final long serialVersionUID = 6427931149119200875L;
	/**
	 * 声明为volatile,保证多线程可见性、防止“instance = new LazyType()”指令重排序。
	 * 注:instance = new LazyType()是非原子性操作。
	 * instance = null:延迟加载。
	 */
	private volatile static LazyType instance = null;  
	
	private static boolean flag = false;
	
	private LazyType() {
		//防止反射攻击
		synchronized (LazyType.class) {
			if (false == flag) {
				flag = !flag;
			} else {
				//被反射攻击,抛出异常。
				throw new RuntimeException("Reflected attack!");
			}
		}
	}
	/**
	 * 执行两次校验:很有必要,这样可以保证线程安全。
	 * 当多线程调用时,如果多个线程同时执行完了第一次检查,其中一个进入他不代码块创建了实例,
	 * 后面的线程因第二次检查不会创建实例。
	 * @return
	 */
	public static LazyType getInstance() {
		if (null == instance) {//第一次检查
			synchronized (LazyType.class) {
				if (null == instance) {//第二次检查
					instance = new LazyType();
				}
			}
		}
		return instance;
	}
	
	/**
	 * 如果实现了Serializable, 必须重写这个方法,才能保证其反序列化依旧是单例,即防止序列化产生新对象。
	 * @return
	 * @throws ObjectStreamException
	 */
	private Object readResolve() throws ObjectStreamException {
		return instance;
	}
	
	/**
	 * 所执行的业务功能
	 */
	public void doSomethingElse() {
		
	}
}

这里有一个关键字我们需要注意 :volatitle

注意:假设没有关键字volatile的情况下,两个线程A、B,都是第一次调用该单例方法,线程A先执行instance = new LazyType();,该构造方法是一个非原子操作,编译后生成多条字节码指令(包括:1、分配初始化对象的内存空间;2、初始化对象;3、设置instance指向刚刚分配的内存空间地址),由于JAVA的指令重排序,可能会先执行instance的赋值操作(即3),后执行初始化对象(即2)该操作实际只是在内存中开辟一片存储对象的区域后直接返回内存的引用,之后instance便不为空了,但是实际的初始化操作却还没有执行,如果就在此时线程B进入,就会看到一个不为空的但是不完整(没有完成初始化)的Instance对象,所以需要加入volatile关键字,禁止指令重排序优化,从而安全的实现单例。

摘录:https://blog.csdn.net/android_freshman/article/details/51029031,对双重检查实现单例(懒汉式加载)的解释

测试,防止单例模式被java反射攻击:

package instance.lazy;
import java.lang.reflect.Constructor;
public class LazyTypeModifiedReflectAttack {
	public static void main(String[] args){
		try {
			Class<LazyType> classType = LazyType.class;
			Constructor<LazyType> c = classType.getDeclaredConstructor(null);
			c.setAccessible(true);
			LazyType e1 = (LazyType)c.newInstance();
			System.out.println("e1.hashCode():" + e1.hashCode());
			LazyType e2 = LazyType.getInstance();
			System.out.println("e2.hashCode():" + e2.hashCode());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

测试结果:

e1.hashCode():118352462
java.lang.RuntimeException: Reflected attack!
	at instance.lazy.LazyType.<init>(LazyType.java:36)
	at instance.lazy.LazyType.getInstance(LazyType.java:50)
	at instance.lazy.LazyTypeModifiedReflectAttack.main(LazyTypeModifiedReflectAttack.java:16)

这么写的具体原因可见:https://www.zhihu.com/question/29971746

此外,如何防止单例模式被JAVA反射攻击: https://blog.csdn.net/u013256816/article/details/50525335

参考内容:

https://blog.csdn.net/wzgiceman/article/details/51809985

https://blog.csdn.net/huangyuan_xuan/article/details/52193006

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页