栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

常用设计模式:单例模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

常用设计模式:单例模式

单例模式

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

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 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 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 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;
	}
}

其他同理

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/860073.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号