1.单例模式常见的几种写法

单例模式

反射和反序列化破坏单例及解决方案

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛。 例如公司 CEO、部门经理等。在 J2EE 标准中,ServletContext、 ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接 池也都是单例形式。

单例模式可以保证内存里只有一个实例,减少了内存开销;可以避免对资源的多重占用。

单例模式特点

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

常见单例模式

单例模式的写法有很多种,这里主要提供四种:饿汉式单例、懒汉式单例、枚举式单例、容器式单例(枚举式单例和容器式单例都属于注册式单例)

饿汉式单例

在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题, Spring 中 IOC 容器 ApplicationContext 本身就是典型的饿汉式单例

  • 优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
  • 缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存。
package org.about.designpatterns.singleton.hungry;

/**
 * @Package org.about.designpatterns.singleton.hungry
 * @Author
 * @Description 饿汉式单例  在类加载的时候就立即初始化,并且创建单例对象。
 * 绝对线程安全,在线程还没出现以前就是实例化了,不可能存在线程安全问题
 * @Version: V1.0
 */
public class HungrySingleton {

	/**
	 * 定义该单例属性
	 */
	private static final HungrySingleton HUNGRY_SINGLETON;

	static {
		HUNGRY_SINGLETON = new HungrySingleton();
	}
	/**
	 * 构造方法私有化
	 */
	private HungrySingleton() {
		// 防止反射破坏单例,下一篇会介绍
		if (HUNGRY_SINGLETON != null) {
			throw new RuntimeException("不允许创建多个单例");
		}
	}

	/**
	 * 提供全局唯一访问点
	 * @return HUNGRY_SINGLETON
	 */
	public static HungrySingleton getInstance() {
		return HUNGRY_SINGLETON;
	}

//	/**
//	 * 防止序列化破坏单例
//	 * 如果该单例实现了  Serializable 序列化接口,为了防止序列化破坏单例,需要重写 readResolve()方法
//	 * 下一篇会讲解序列化破坏单例
//	 * @return SINGLETON
//	 */
//	private Object readResolve() {
//		return HUNGRY_SINGLETON;
//	}
}

懒汉式单例

被外部类调用的时候内部类才会加载单例对象,避免了饿汉式在类加载的时候就产生实例而产生性能消耗

双重检查锁写法

package org.about.designpatterns.singleton.lazy;

/**
 * @Package org.about.designpatterns.singleton.lazy
 * @Author Epocher
 * @Description 懒汉模式: 双重检查锁
 * <p>
 *     知识点: 多线程下指令重排问题  volatile
 *     (this) lazy = new LazyDoubleCheckSingleton();
 *     cpu执行上面的时候会转换为JVM指令:1.分配内存给这个对象; 2.属性初始化; 3.引用指向对象
 *     上面语句为非原子性,所以上面可能会产生指令重排问题 即:正常情况下执行顺序为 1->2->3 但是实际情况下可能就是 1->3->2
 *
 *     (1)单线程情况下,指令重排没有影响;
 *     (2)但在多线程情况下,假如线程(1)执行 lazy = new LazyDoubleCheckSingleton()语句时先1 再 3,
 *     但是此时系统调度线程(2),没来得及执行步骤2,但此时已有引用指向对象
 *     (即已经执行3 但是没有完成初始化2。此时对象处于半初始化状态,但对象此时已经不为null了。但是还没属性初始化)
 *     故线程2在第一次检查时不满足条件直接返回 lazy,此时 lazy虽然不为null 但是内部属性还没有完成初始化。
 *     volatile 关键字可保证 lazy = new LazyDoubleCheckSingleton();的语句执行顺序为 1 2 3
 *     具体可以参考 volatile 的特性。这里就不做过多说明了
 * </p>
 * @Date 2020-07-28 16:35
 * @Version: V1.0
 */
public class LazyDoubleCheckSingleton {
	/**
	 * LazyDoubleCheckSingleton 实例属性
	 * volatile 关键字禁止指令重排, 即为第二层锁
	 */
	private volatile static LazyDoubleCheckSingleton lazy;

	/**
	 * 构造方法私有化
	 */
	private  LazyDoubleCheckSingleton() {
		// 这个是为了防止 通过反射机制从而破坏了单例模式
		// 下一篇会做讲解
		if (lazy != null) {
			throw new RuntimeException("不允许创建多个单例!");
		}
	}

	/**
	 * 提供全局唯一访问点
	 * <p>
	 *     synchronized 虽然在性能上已经有了优化,但是还是不可避免的产生内存的消耗
	 *     下面会介绍更加好的单例模式
	 * </p>
	 * @return lazy
	 */
	public static LazyDoubleCheckSingleton getInstance() {
	    // Double Check Lock 双重监察锁的机制来实现,(DCL)
		// 第一层检查,检查是否有引用指向对象,高并发情况下会有多个线程同时进入 的时候才创建单例,避免重复创建
		// lazy == null同时避免每个线程进来的时候首先竞争锁,从而消耗资源,这样每个线程进来先判断是否已经创建过,没创建对象的情况下才会竞争锁。这样对资源
		if (lazy == null ) {
			// synchronized 锁,synchronized保证只有一个线程进入
			synchronized (LazyDoubleCheckSingleton.class) {
				// 第二层检查,若不检查,那么第一个线程创建完对象释放锁后,后面进入对象也会创建对象,会产生多个对象
				if (lazy == null) {
					// 利用 volatile 关键字防止指令重排
					lazy = new LazyDoubleCheckSingleton();
				}
			}
		}
		return lazy;
	}

//	/**
//	 * 防止序列化破坏单例
//	 * 如果该单例实现了  Serializable 序列化接口,为了防止序列化破坏单例,需要重写 readResolve()方法
//	 * 下一篇会讲解序列化破坏单例
//	 * @return SINGLETON
//	 */
//	private Object readResolve() {
//		return lazy;
//	}
}

静态内部类写法

利用静态内部类加载的特性:不会自动初始化,只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类。
利用JVM底层特性,巧妙的避免了线程安全问题。性能相对来说很优秀

package org.about.designpatterns.singleton.lazy;

/**
 * @Package org.about.designpatterns.singleton.lazy
 * @Author
 * @Description 静态内部类实现懒汉式单例 性能最好 全程没有用到 synchronized关键字
 * @Version: V1.0
 */
public class LazyInnerClassSingleton {

	/**
	 * 构造方法私有化
	 */
	private LazyInnerClassSingleton(){
		// 这个是为了防止 通过反射机制从而破坏了单例模式
		// 下一篇会做讲解
		if (LazyHolder.LAZY != null) {
			throw new RuntimeException("不允许创建多个单例!");
		}
	}

	/**
	 * 全局唯一访问点
	 * static 是为了使单例的空间共享
	 * final 保证这个方法不会被重写,重载
	 * @return LazyHolder.LAZY
	 */
	public static final LazyInnerClassSingleton getInstance() {
		// 在返回结果以前,一定会先加载内部类
		return LazyHolder.LAZY;
	}

	/**
	 * 静态内部类 利用了静态内部类 加载时的特性
	 * 内部静态类不会自动初始化,只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类。
	 * JVM 底层的逻辑,完美的避免了线程安全问题
	 */
	private static class LazyHolder {
		private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
	}

//	/**
//	 * 防止序列化破坏单例
//	 * 如果该单例实现了  Serializable 序列化接口,为了防止序列化破坏单例,需要重写 readResolve()方法
//	 * 下一篇会讲解序列化破坏单例
//	 * @return SINGLETON
//	 */
//	private Object readResolve() {
//		return LazyHolder.LAZY;
//	}
}

饿汉模式与懒汉模式

  • 时间和空间
    • 饿汉式:是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间。
    • 懒汉式:是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间
  • 线程安全
    • 饿汉式:线程绝对安全,因为在类加载的时候就已经初始化了对象实例。JVM只会装载一次,在并发发生之前就已经创建了唯一实例
    • 懒汉式:存在线程安全问题。好的解决方案是 上面的双重检查锁和静态内部类写法

注册式单例

注册式单例:又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记

枚举式单例

不仅能解决多线程同步问题,而且能防止反序列化重新创建新的对象和反射破坏单例。
因为是java底层对枚举进行单独的处理。而枚举出现的也对较晚jdk1.5,所以使用的频率也不是特别高
,本质上也属于饿汉式单例,但是很多书籍都推荐的一种写法

package org.about.designpatterns.singleton.register;

/**
 * @Package org.about.designpatterns.singleton.register
 * @Author 
 * @Description 注册式: 枚举式单例
 * @Version: V1.0
 */
public enum EnumSingleton {
	
	INSTANCE;

	private Object data;

	public Object getData() {
		return data;
	}

	public void setData(Object data) {
		this.data = data;
	}

	public static EnumSingleton getInstance() {
		return INSTANCE;
	}
}

容器式单例

  • 相当于有一个容器装载所有实例,在实例产生之前先检查下容器有没有,如果有就直接取出来,如果没有就先new一个放进去,然后给后面的人用,SpringIoc容器就是一种注册登记式单例
  • 登记式单例实际上维护了一种单例类的实例,将这些实例存放在一个Map中,对于已经登记过的实例,则从Map直接返回,没有登记的,则先登记,然后返回;
  • 登记式单例内部实现其实还是用的饿汉式,因为其中的static方法块,它的的单例在类被装载时就被实例化了
  • 不加锁的话会存在线程安全问题
package org.about.designpatterns.singleton.register;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Package org.about.designpatterns.singleton.register
 * @Author
 * @Description 容器式单例
 * @Version: V1.0
 */
public class ContainerSingleton {
	/**
	 * 构造方法私有化
	 */
	private ContainerSingleton() {}
	
	private static Map<String, Object> ioc = new ConcurrentHashMap<>();

	public static Object getBean(String className) {
		// 防止线程安全问题
		// 对象方便管理,其实也属于懒加载
		synchronized (ioc) {
			if (!ioc.containsKey(className)) {
				Object obj = null;
				try {
					// 这里是简单工厂模式
					obj = Class.forName(className).newInstance();
					ioc.put(className, obj);
				} catch (Exception e) {
					e.printStackTrace();
				}
				// 不存在就创建一个,然后返回
				return obj;
			}
			// 存在就直接返回 object
			return ioc.get(className);
		}
	}
}

以上就是几种比较常见的单例。

posted @ 2025-06-04 14:42  Anzimer  阅读(55)  评论(0)    收藏  举报