设计模式-创建型-单例模式
单例模式
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。
单例模式是创建型模式,Spring框架中的ApplicationContext、J2EE中的ServletContext和ServletContextConfig、数据库的连接池都是单例模式
1、饿汉单例模式
饿汉单例模式在类加载的时候就立即初始化,并且创建单例对象。
绝对的线程安全,在线程还没有出现以前就实例化了,不可能存在访问安全问题。
-
优点:没有任何加锁操作、执行效率比较高,用户体验比懒汉单例模式更好
-
缺点:类加载的时候就初始化,不管用不用都占空间,浪费内存
1.1、饿汉单例案例
经典案例:
/** * TODO 懒汉单例模式 * 饿汉单例模式在类加载的时候就立即初始化,并且创建单利对象 * 绝对线程安全 * @author ss_419 * @version 1.0 * @date 2023/9/1 09:10 */ public class HungrySingleton { // 先静态,后动态 // 先属性,后方法 // 先上后下 private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton(); private HungrySingleton() {} // 通过getInstance返回对象实例 public static HungrySingleton getInstance(){ return HUNGRY_SINGLETON; } }
改进写法,使用静态代码块机制:
/** * TODO 静态饿汉单例模式 * 利用静态代码块的机制 * 饿汉单例模式适用于单例对象较少的情况 * @author ss_419 * @version 1.0 * @date 2023/9/1 09:16 */ public class HungryStaticSingleton { private static final HungryStaticSingleton INSTANCE ; /** * 通过静态代码块对类进行实例化 */ static { // 静态代码块,在初始化之前的连接阶段中的准备阶段就对静态变量分配内存、设置初始值等操作 INSTANCE = new HungryStaticSingleton(); } /** * 不能通过构造器对类进行实例化操作 */ private HungryStaticSingleton() { } // 提供一个全局访问点,以供实例化对象 public static HungryStaticSingleton getInstance() { return INSTANCE; } }
2、懒汉单例模式
特点:被外部类调用的时候内部类才会加载
2.1、懒汉单例案例
懒汉单例模式在外部需要使用的时候才进行实例化:
/** * TODO 懒汉单例模式 * 懒汉单例模式:在外部需要使用的时候才进行实例化 * * @author ss_419 * @version 1.0 * @date 2023/9/1 09:20 */ public class LazySimpleSingleton { private LazySimpleSingleton() { } // 静态块,公共内存区域 private static LazySimpleSingleton lazy = null; // 提供全局访问点,实例化单例对象 public static LazySimpleSingleton getInstance() { if (lazy == null){ // 对象没有创建的时候才new,否则就返回之前所存在的对象 return new LazySimpleSingleton(); } return lazy; } }
创建一个线程类ExectorThread:
public class ExectorThread implements Runnable{ @Override public void run() { // 通过懒汉单例中提供的全局访问点创建一个单例对象 LazySimpleSingleton singleton = LazySimpleSingleton.getInstance(); // 查看线程中存在的对象 System.out.println(Thread.currentThread().getName() + ": " + singleton); } }
测试代码:
@Test public void test1(){ Thread t1 = new Thread(new ExectorThread()); Thread t2 = new Thread(new ExectorThread()); // 这里测试,出现两个类不同的情况,单例存在线程安全 问题 t1.start(); t2.start(); System.out.println("End"); }
通过测试,上面的代码存在线程安全问题,怎么样才能使线程安全呢?
第一个方法:通过对获得实例方法进行加锁操作,保证当前线程获得实例时,其他线程都处于阻塞状态
package org.pp.my.design_pattern.create.singleton2.lazy; /** * TODO 懒汉单例模式 * 懒汉单例模式:在外部需要使用的时候才进行实例化 * * @author ss_419 * @version 1.0 * @date 2023/9/1 09:20 */ public class LazySimpleSingleton { private LazySimpleSingleton() { } // 静态块,公共内存区域 private static LazySimpleSingleton lazy = null; /** * 为了保证懒汉单例模式在多线程环境下线程安全,通过synchronized加锁实现 * @return */ public synchronized static LazySimpleSingleton getInstance() { if (lazy == null){ // 对象没有创建的时候才new,否则就返回之前所存在的对象 return new LazySimpleSingleton(); } return lazy; } }
但是通过synchronized加锁的方式,会使性能降低
使用双重锁既能兼顾线程安全又能提升程序性能:
/** * TODO 懒汉模式双重检查锁 * * @author ss_419 * @version 1.0 * @date 2023/9/1 09:34 */ public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton lazy = null; private LazyDoubleCheckSingleton(){ } public static LazyDoubleCheckSingleton getInstance() { if (lazy == null){ synchronized (LazyDoubleCheckSingleton.class){ lazy = new LazyDoubleCheckSingleton(); // 1、分配内存给这个对象 // 2、初始化对象 // 3、设置lazy指向刚分配的内存地址 } } return lazy; } }
采用静态内部类方式,避免加锁操作
/** * TODO 懒汉模式-静态内部类 * 通过静态内部类来避免使用synchronized加锁的情况 * @author ss_419 * @version 1.0 * @date 2023/9/1 09:39 */ public class LazyInnerClassSingleton { // 使用LazyInnerClassSingleton,默认会先初始化内部类 // 如果没使用,则内部类是不加载的 private LazyInnerClassSingleton(){ } // 每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不会被重写、重载 public static final LazyInnerClassSingleton getInstance(){ return LazyHolder.LAZY; } private static class LazyHolder{ private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
2.2、反射破坏单例
通过反射机制,获取类的私有构造方法,强制访问
/** * 通过反射来破坏单例 */ @Test public void test2(){ try { // 反射获取单例类 Class<LazyInnerClassSingleton> clazz = LazyInnerClassSingleton.class; // 通过反射获取私有的构造方法 Constructor<LazyInnerClassSingleton> c = clazz.getDeclaredConstructor(null); // 强制访问 c.setAccessible(true); // 暴力初始化 Object o1 = c.newInstance(); // 调用了两次构造方法,相当于“new”了两次,犯了原则性错误 Object o2 = c.newInstance(); System.out.println("o1 = " + o1); System.out.println("o2 = " + o2); System.out.println(o1 == o2); }catch (Exception e){ } }
为了避免上述情况发生,在构造器中做一些操作,一旦出现多次重复创建,则直接抛出异常
/** * TODO 史上最强的懒汉单例模式 * 一旦出现多次重复创建,则直接抛出异常 * * @author ss_419 * @version 1.0 * @date 2023/9/1 09:51 */ public class LazyInnerClassNoNullSingleton { // 使用LazyInnerClassGeneral的时候,默认会先初始化内部类 // 如果没有使用,则内部类是不加载的 private LazyInnerClassNoNullSingleton(){ if (LazyHolder.LAZY != null){ // 不为null,说明对象已经实例化过了 throw new RuntimeException("不允许创建多个实例"); } } /** * 每一个关键字都不是多余的 * static是为了单例的空间共享,保证这个方法不会被重写、重载 * @return */ public static LazyInnerClassNoNullSingleton getInstance(){ // 在返回之前,一定会先加载内部类 return LazyHolder.LAZY; } /** * 默认不加载 */ private static class LazyHolder { private static final LazyInnerClassNoNullSingleton LAZY = new LazyInnerClassNoNullSingleton(); } }
posted on 2023-09-01 13:39 JavaCoderPan 阅读(9) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南