java设计模式之单例模式

单例模式(Singleton Pattern)属于创建型模式,它提供了一种创建对象较好的方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时保证只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  1. 一个类只能有一个实例
  2. 单例类必须自己创建自己的唯一实例。
  3. 单例类必须给所有其他对象提供这一实例。

单例模式的目的:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

使用场景:向想确保任何情况下都只有一个实例(线程池、数据库连接池等)

优点:在内存里只有一个实例,减少了内存开销、可以避免对资源的多重占用、设置全局 访问点,严格控制访问

缺点:没有接口、扩展困难

重点:私有构造器、线程安全、延迟加载、序列化和反序列化安全、反射

相关设计模式:单例模式和工厂模式、单例模式和享元模式

单例模式的集中实现方式:

  • 懒汉式(线程不安全)
    懒汉式单例模式,顾名思义,这种单例模式很懒,在初始化的时候不创建,而是做一个延迟加载。这种实现方式最大的问题是在多线程的场景下不安全,没有加 synchronized 锁 。
public class LazySingleton {
	private static LazySingleton lazySingleton = null;
	private LazySingleton() {
		
	}
	public synchronized static LazySingleton getInstance() {
		if(lazySingleton == null) {
			lazySingleton = new LazySingleton();
		}
		return lazySingleton;
	}
}
  • DoubleCheck双重检查实现懒汉式(线程安全)
    上述延迟加载的模式在多线程的情况下是不安全的,为了解决多线程问题,可以采用DoubleCheck双重检查实现懒汉式。这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是效率很低。
public class LazyDoubleCheckSingleton {
	private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
	private LazyDoubleCheckSingleton() {
		
	}
	public synchronized static LazyDoubleCheckSingleton getInstance() {
		if(lazyDoubleCheckSingleton == null) {
			synchronized (LazyDoubleCheckSingleton.class) {
				if(lazyDoubleCheckSingleton == null) {
					lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
				}
			}
		}
		return lazyDoubleCheckSingleton;
	}
}
  • 静态内部类(线程安全)
    基于类初始化的延迟加载,这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
public class StaticInnerClassSingleton {
	// InnerClass类的对象的初始化锁被哪个线程拿到,那个线程负责实例化对象
	private static class InnerClass {
		private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
	}
	
	public static StaticInnerClassSingleton getInstance() {
		return InnerClass.staticInnerClassSingleton;
	}
	
	private StaticInnerClassSingleton() {
		
	}
}
  • 饿汉式(线程安全)
    在类加载的时候就完成类的实例化。这种方式比较常用,但容易产生垃圾对象。它没有加锁,执行效率会提高,但是类加载时就初始化,浪费内存。它基于 classloader 机制避免了多线程的同步问题。
public class HungrySingleton {
	private final static HungrySingleton hungrySingleton;
	
	static {
		hungrySingleton = new HungrySingleton();
	}
	
	private HungrySingleton() {
		
	}
	
	public static HungrySingleton getInstance() {
		return hungrySingleton;
	}
	
}
  • 序列化破坏单例模式原理解析及解决方案(以饿汉式为例)
    在序列化和反序列化之后拿到的不是同一个对象。这是因为ObjectInputStream 对象的readObject()方法调用了readObject0()方法,而当ObjectInputStream 读入的是对象时,readObject0()方法中的 readOrdinaryObject(unshared)方法会调用isInstantiable()方法,isInstantiable()方法在类运行时被实例化时会返回true,然后会调用newInstance()方法,这样就通过反射又创建了一个对象,所以在序列化和反序列化之后拿到的不是同一个对象。
		HungrySingleton instance = HungrySingleton.getInstance();
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
		oos.writeObject(instance);
		
		File file = new File("singleton_file");
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
		HungrySingleton newInstance = (HungrySingleton)ois.readObject();
		
		System.out.println(instance);
		System.out.println(newInstance);
		// 结果为 false ,说明两者不是同一个对象,这就违背了单例设计模式
		System.out.println(instance == newInstance);
		oos.close();
		ois.close();

解决方案

public class HungrySingleton implements Serializable {
	private static final long serialVersionUID = 1L;
	private final static HungrySingleton hungrySingleton;
	
	static {
		hungrySingleton = new HungrySingleton();
	}
	
	private HungrySingleton() {
	
	}
	
	public static HungrySingleton getInstance() {
		return hungrySingleton;
	}
	
	public Object readResolve() {
		return hungrySingleton;
	}
}
  • 反射攻击解决方案及原理分析(以饿汉式为例)
		Class objectClass = HungrySingleton.class;
		Constructor constructor = objectClass.getDeclaredConstructor();
		constructor.setAccessible(true);
		HungrySingleton instance = HungrySingleton.getInstance();
		HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
		System.out.println(instance);
		System.out.println(newInstance);
		// 结果为 false ,说明两者不是同一个对象,这就违背了单例设计模式
		System.out.println(instance == newInstance);

解决方案

public class HungrySingleton {
	private final static HungrySingleton hungrySingleton;
	
	static {
		hungrySingleton = new HungrySingleton();
	}
	
	private HungrySingleton() {
		if(hungrySingleton != null) {
			throw new RuntimeException("单例构造器禁止反射调用");
		}
	}
	
	public static HungrySingleton getInstance() {
		return hungrySingleton;
	}
	
}
  • Enum枚举单例(线程安全)
    这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
    这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
public enum EnumInstance {
	INSTANCE;
	private Object date;

	public Object getDate() {
		return date;
	}

	public void setDate(Object date) {
		this.date = date;
	}
	
	public static EnumInstance getInstance() {
		return INSTANCE;
	}
}
  • 容器单例模式(线程安全问题取决于具体的实现)
    容器单例的思路是这样的,维护一个Map,把单例的实例都存放进该Map中,用的时候只从该Map中取,试图实现单例;但这种思路有局限性,Map中存的单例对象是可以被更新掉的,如果两次取的间隔,发生了单例对象的更新,就会取到2个不同的对象,破坏了单例性;

    应用场景:如果程序中单例类很多,可以考虑用一个容器管理起来;

public class ContainerSingleton {
	private static Map<String, Object> singletonMap = new HashMap<String, Object>(); 
	private ContainerSingleton() {
		
	}
	public static void putInstance(String key, Object instance) {
		if(instance != null) {
			if(!singletonMap.containsKey(key)) {
				singletonMap.put(key, instance);
			}
		}
	}
	
	public static Object getInstance(String key) {
		return singletonMap.get(key);
	}
}
  • ThreadLocal 线程“单例模式”
    不是传统意义中的单例模式,不同线程之间拿到的对象可能不同,但是每个线程中对象都是单例的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值