常用设计模式:单例模式

单例模式

单例的定义:保证一个类只有一个实例,并且提供一个全局访问点
使用场景:重量级的对象,不需要多个实例,如线程池,数据库连接池‘’
实现方式:
懒汉式
延迟加载, 只有在真正使用的时候,才开始实例化

public class LazySingletonTest {
	public static void main(String[] args) {
		LazySingleton instance1 = LazySingleton.getinstance();
		LazySingleton instance2 = LazySingleton.getinstance();
		//得到的结果为true
		System.out.println(instance1 == instance2);
	}
}
//懒加载
class LazySingleton {
	private static LazySingleton instance;
	private LazySingleton(){}
	public static LazySingleton getinstance(){
		if (instance == null){
			instance = new LazySingleton();
		}
		return instance;
	}

}

上面的代码是一个简单的实现,在并发场景会出现问题

public class LazySingletonTest {
	private static final CountDownLatch cdl = new CountDownLatch(1);
	public static void main(String[] args) throws InterruptedException {
		new Thread(()->{
			try {
				cdl.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(LazySingleton.getinstance());
		}).start();
		new Thread(()->{
			try {
				cdl.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(LazySingleton.getinstance());
		}).start();

		Thread.sleep(100);
		cdl.countDown();
	}
}

在这里插入图片描述
得到的实例不相同
想解决并发问题很简单 加个锁

class LazySingleton {
	private static LazySingleton instance;
	private LazySingleton(){}
	public synchronized static LazySingleton getinstance(){
		if (instance == null){
			instance = new LazySingleton();
		}
		return instance;
	}
}

再次执行得到的结果就会相同
在这里插入图片描述
当然加了锁以后,会出现这么一个问题,如果是已经初始化的也会进行加锁操作,影响性能
继续优化:通过双重检查锁 DCL的方式

class LazySingleton {
	private static LazySingleton instance;
	private LazySingleton(){}
	public static LazySingleton getinstance(){
		if (instance == null){
			synchronized (LazySingleton.class){
				if (instance == null){
					instance = new LazySingleton();
				}
			}
		}
		return instance;
	}
}

但是这样还会出现问题,在new LazySingleton(); 这个阶段会出现一个临界区,这个临界区有这三个步骤, 1 .分配空间 2 .初始化 3 .引用赋值。这三个步骤有可能进行指令重排。为了防止2 3进行指令重排,还要进行优化
在LazySingleton 加上 volatile 关键字禁止指令重排

class LazySingleton {
	private volatile static LazySingleton instance;
	private LazySingleton(){}
	public static LazySingleton getinstance(){
		if (instance == null){
			synchronized (LazySingleton.class){
				if (instance == null){
					instance = new LazySingleton();
				}
			}
		}
		return instance;
	}
}

饿汉模式
类加载的 初始化阶段就完成了 实例的初始化 。本质上就是借助于jvm 类加载机制,保证实例的唯一性(初始化过程只会执行一次)及线程安全(JVM以同步的形式来完成类加载的整个过程)。

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

静态内部类
.本质上是利用类的加载机制来保证线程安全
只有在实际使用的时候,才会触发类的初始化,所以也是懒加载的一 种形式。

public class InnerClassSingleton {
	private InnerClassSingleton(){}
	private static class InnerClassHolder{
		private static InnerClassSingleton instance =new InnerClassSingleton();

	}
	public static InnerClassSingleton getInstance(){
		return InnerClassHolder.instance;
	}
}

反射攻击导致单例失效

public static void main(String[] args) throws Exception{
		Constructor<LazySingleton> constructor = LazySingleton.class.getDeclaredConstructor();
		constructor.setAccessible(true);
		LazySingleton lazySingleton = constructor.newInstance();
		LazySingleton lazySingleton2 = constructor.newInstance();
		System.out.println(lazySingleton == lazySingleton2); //结果为false
	}

反射创建实例会导致懒汉式的单例失效,同理也会导致静态内部类和饿汉式失效,但是静态内部类和饿汉式可以防止反射攻击

饿汉式防止反射攻击

public class HungrySingleton {
	private static HungrySingleton instance = new HungrySingleton();
	private HungrySingleton(){
		if(instance != null){
			throw new RuntimeException( " 单例不允许多个实例 " );
		}
	}
	public static HungrySingleton getInstance(){
		return instance;
	}
}

静态内部类防止反射攻击

public class InnerClassSingleton {
	private InnerClassSingleton(){
		if (InnerClassHolder.instance != null){
			throw new RuntimeException( " 单例不允许多个实例 " );
		}
	}
	private static class InnerClassHolder{
		private static InnerClassSingleton instance =new InnerClassSingleton();

	}
	public static InnerClassSingleton getInstance(){
		return InnerClassHolder.instance;
	}
}

测试:

public static void main(String[] args) throws Exception{
		Constructor<HungrySingleton> constructor = HungrySingleton.class.getDeclaredConstructor();
		constructor.setAccessible(true);
		HungrySingleton hungrySingleton = constructor.newInstance();
		HungrySingleton hungrySingleton2 = constructor.newInstance();
		System.out.println(hungrySingleton == hungrySingleton2);
	}

在这里插入图片描述
直接抛出异常

枚举方式创建单例
天然不支持反射创建对应的实例,且有自己的反序列化机制;利用类加载机制保证线程安全
枚举和类一样可以定义属性,方法等

public enum EnumSingleton {
	INSTANCE("test",5);
	public void print(){
		System.out.println(this.hashCode());
	}
	private String aa;
	private Integer bb;
	private EnumSingleton(String aa,Integer bb){
		this.aa = aa;
		this.bb = bb;
	}
	public String getAa(){return aa;}
	public int getBb(){return bb;}

}

测试:

public static void main(String[] args) throws Exception{
		EnumSingleton instance = EnumSingleton.INSTANCE;
		EnumSingleton instance2 = EnumSingleton.INSTANCE;
		System.out.println(instance == instance2);

		Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class,Integer.class);
		constructor.setAccessible(true);
		EnumSingleton enumSingleton = constructor.newInstance("test",5);
		EnumSingleton enumSingleton2 = constructor.newInstance("test",5);
		System.out.println(enumSingleton == enumSingleton2);
	}

在这里插入图片描述
反序列化导致单例失效
public class InnerClassSingleton implements Serializable InnerClassSingleton 实现序列化
将该类的实例序列化到磁盘上

public static void main(String[] args) throws Exception{
		InnerClassSingleton instance = InnerClassSingleton.getInstance();
		//序列化
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testinner"));
		oos.writeObject(instance);
		oos.flush();
		oos.close();
		//反序列化
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testinner"));
		InnerClassSingleton instance2 = (InnerClassSingleton)ois.readObject();
		System.out.println(instance == instance2); //结果为false
	}

在这里插入图片描述
解决方案:在Serializable这个接口的注释中给出了解决方案
在这里插入图片描述
在InnerClassSingleton 重写这个方法 InnerClassSingleton 代码最终如下

public class InnerClassSingleton implements Serializable {
	static final long serialVersionUID = 42L;
	private InnerClassSingleton(){
		if (InnerClassHolder.instance != null){
			throw new RuntimeException( " 单例不允许多个实例 " );
		}
	}
	private static class InnerClassHolder{
		private static InnerClassSingleton instance =new InnerClassSingleton();

	}
	public static InnerClassSingleton getInstance(){
		return InnerClassHolder.instance;
	}

	Object readResolve() throws ObjectStreamException {
		return InnerClassHolder.instance;
	}
}

其他同理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值