设计模式之单例模式

单例模式

定义:保证一个类只有一个实例,并且提供一个全局访问点

场景:线程池,数据库连接池等

实现方式:

1.懒汉模式:

只有使用的时候,才初始化。延迟加载

//使用的时候才初始化
//jvm 一个实例
public class LazySingleton {
	private volatile static LazySingleton instance;	//volatile防止CPU指令重排
	private LazySingleton(){}
	public static LazySingleton getInstance() {
		if(null==instance){		//双重检查
			synchronized(LazySingleton.class){
				if(null==instance)
					instance=new LazySingleton();
			}
		}
		return instance;
	}

}

给变量加volatile是为了防止CPU指令重排,我们来看下方的Demo例子:

public class Demo {

	public static void main(String[] args) {
		Demo demo=new Demo();
	}
}
0: new	         #1    	// class Demo            1.分配空间,返回一个指向该空间的内存引用
3: dup
4: invokespecial #16    // Method "<init>":()V   2.对空间进行初始化
7: astore_1             //                       3.把内存引用赋值给Demo变量

“4:”和“7:”这两个指令顺序可能会颠倒(reordering),也就是先把内存引用赋值给Demo变量,然后对空间进行初始化。这时候就出现问题了,我们看LazySingleton这个类:如果内存引用赋值给instance变量后,还没有进行初始化,这时候另外一个线程进入"getInstance()"方法,他会发现这个instance已经被赋值了,就会直接执行“return instance”来返回实例,但是这个实例还没有被初始化,这就可能会导致空指针等异常。所以,我们要在变量前加volatile修饰来防止CPU重排。

总结:

(1).保证线程安全

(2).防止指令重排

(3).双重检查,优化加锁过程

2.饿汉模式:

在类加载阶段就完成了实例的初始化,通过类加载机制保证线程安全

类加载过程:

(1).类加载:加载对应的二进制文件,在方法区创建对应的数据结构 (加载class文件到方法区,在方法区创建相应类对象)

(2).连接:a.验证(确保Class文件的正确性)  b.准备(为静态变量分配内存和设置初始值)  c.解析(符号引用替换为直接引用)

(3).初始化:给静态属性赋值

public class HungrySingleton {
	private static HungrySingleton instance=new HungrySingleton();
	public static HungrySingleton getInstance(){
		return instance;
	}
	private HungrySingleton() {}
	
}

3.静态内部类:

public class InnerClassSingleton {
	static class InnerClass{
		private static InnerClassSingleton instance=new InnerClassSingleton();
	}
	
	public static InnerClassSingleton getInstance(){
		return InnerClass.instance;
	}
	
	private InnerClassSingleton() {
		if(InnerClass.instance!=null){
			throw new RuntimeException("单例类不允许多个实例(不允许反射来破坏单例模式)");
		}
	}
}

在外界调用"getInstance()" 的时候,内部类InnerClass才会被类加载器加载,然后在加载过程中初始化instance。为了防止遭到反射攻击,可以在私有构造器中加入初始化判断,如果instance已被初始化,就说明单例类遭到破坏,此时可以向外界抛出异常

4.枚举类型:

public enum EnumSingleton {
	INSTANCE;
}

反射机制不允许实例化枚举类型,源码如下:

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
	throw new IllegalArgumentException("Cannot reflectively create enum objects");

枚举类型也可以防止反序列化攻击,源码如下:

String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
    try {
        @SuppressWarnings("unchecked")
        Enum<?> en = Enum.valueOf((Class)cl, name);
        result = en;
    } catch (IllegalArgumentException ex) {
        throw (IOException) new InvalidObjectException(
            "enum constant " + name + " does not exist in " +
            cl).initCause(ex);
    }
    if (!unshared) {
        handles.setObject(enumHandle, result);
    }
}

 当使用反序列化获取枚举对象时,会通过"valueOf()"方法来根据名字(name)查找枚举对象。

5.序列化:

public class SerializableSingleton implements Serializable{
	static final long serialVersionUID=42L;
	private static SerializableSingleton instance=new SerializableSingleton();
	public static SerializableSingleton getInstance(){
		return instance;
	}
	private SerializableSingleton() {
	}
	
	//反序列化时,从此方法中拿取实例
	Object readResolve() throws ObjectStreamException{
		return instance;
	}
}

serialVersionUID:如果我们没有设置这条属性,系统会默认的帮我们随机分配一个UID,这时候我们将这个类序列化到磁盘上。然后当我们修改这个类中的内容的时候,系统又会随机再分配一个UID。此时修改后的类和磁盘上的类因为UID不同,所以互相不兼容。即:SerializableSingleton instance != (SerializableSingleton)object

readResolve():在反序列化时获得的对象和通过"getInstance()"方法获得的对象不是同一个对象,为了解决这个问题,我们写一个特殊方法:"readResolve()",有了这个方法,在反序列化过程中,就会从这个方法中获得该类的实例。

我们用ObjectOutputStream和ObjectInputStream来写出和读入:

private static void useSerializableSingleton() throws Exception {
	SerializableSingleton instance=SerializableSingleton.getInstance();
	ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("singleton"));
	objectOutputStream.writeObject(instance);
	objectOutputStream.close();
	
	ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("singleton"));
	Object object=objectInputStream.readObject();
	objectInputStream.close();
	
	SerializableSingleton instance1=(SerializableSingleton)object;
	System.out.println(instance1);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值