⭐简单说两句⭐
✨ 正在努力的小叮当~
💖 超级爱分享,分享各种有趣干货!
👩💻 提供:模拟面试 | 简历诊断 | 独家简历模板
🌈 感谢关注,关注了你就是我的超级粉丝啦!
🔒 以下内容仅对你可见~作者:小叮当撩代码,CSDN后端领域新星创作者 |阿里云专家博主
CSDN个人主页:小叮当撩代码
🔎GZH:
哆啦A梦撩代码
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
面试官听了我说的单例设计模式,让我出门右转
文章目录
- 面试官听了我说的单例设计模式,让我出门右转
- 🤣前言
- 📖概念
- 📋 应用场景
- 🥲懒汉式-线程不安全
- 🙊懒汉式-线程安全
- 💓饿汉式
- 💖枚举实现
- 🍇容器实现
🤣前言
我走进面试室,面试官一脸严肃地递给我一张纸和一支笔,说:“请手写一个单例模式。
”我微微一笑,深吸一口气,然后开始在纸上画起了小鸡啄米图,边画边说:“这是单例鸡,它只能有一个,多了就乱套了。”面试官愣住了,然后你接着解释:“单例模式嘛,就像这个小鸡,无论世界多大,它都是独一无二的。” 🤣🤣🤣
好勒,开个玩笑,我们下面开始正式开始单例模式手搓环节哈🤣
📖概念
单例设计模式是一种确保一个类在整个应用程序中只有一个实例存在的设计模式。这意味着无论在何处访问该类,得到的都是同一个实例对象。它通过限制类的构造函数的访问权限,以及提供一个静态方法来获取唯一的实例,实现了实例的唯一性控制。
📋 应用场景
- 日志记录器:通常只需要一个全局的日志记录对象来记录应用的所有日志。
- 数据库连接池:管理有限数量的数据库连接,保证连接的高效使用和共享。
- 配置文件读取:读取配置信息的类,因为配置在整个应用中是全局且唯一的。
- 线程池:管理和复用线程资源
- 等等
🥲懒汉式-线程不安全
懒汉式-线程不安全-代码清单
/*** @author tiancx* @description: 懒汉式单例模式-线程不安全* @date 2024年07月20日*/
public class LazySingleton {private static LazySingleton lazySingleton;public LazySingleton(){}public static LazySingleton getInstance(){if(Objects.isNull(lazySingleton)){lazySingleton = new LazySingleton();}return lazySingleton;}
}
我们在测试类中运行 - 代码清单
import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.*;public class SingletonTest {@Testvoid getInstance() {for (int i = 0; i < 1000; i++) {new Thread(() -> {LazySingleton lazySingleton1 = LazySingleton.getInstance();LazySingleton lazySingleton2 = LazySingleton.getInstance();assertEquals(lazySingleton1, lazySingleton2);}).start();}}
}
🔔:这里得多试几次才可能看到这种问题,同时也说明这种写法是线程不安全的
🙊懒汉式-线程安全
上面的懒汉式存在线程不安全问题,怎么解决呢,最简单的办法就是直接在getInstance方法前面加个锁synchronized
/*** @author tiancx* @description: 懒汉式单例模式-线程安全-效率低* @date 2024年07月20日*/
public class ThreadSafeLazySingleton {private static ThreadSafeLazySingleton threadSafeLazySingleton;public ThreadSafeLazySingleton(){}public static synchronized ThreadSafeLazySingleton getInstance(){if(Objects.isNull(threadSafeLazySingleton)){threadSafeLazySingleton = new ThreadSafeLazySingleton();}return threadSafeLazySingleton;}
}
🤓这种写法是没有问题的,不会有线程安全问题,但是直接锁住方法效率相对来说会比较低下
但是啊,计算机的大神特别的多啊,想着啊,人岂能无翻身之日,他们便想到了一个妙操作(双重检查机制)
双重检查锁(Double-Checked Locking)是一种用于优化单例模式在多线程环境下性能的技术。
代码清单
/*** @author tiancx* @description: 懒汉式单例模式-线程安全-双重检查* @date 2024年07月20日*/
public class DoubleCheckSingleton {private static volatile DoubleCheckSingleton doubleCheckSingleton;public DoubleCheckSingleton(){}public static DoubleCheckSingleton getInstance(){if(Objects.isNull(doubleCheckSingleton)){synchronized (DoubleCheckSingleton.class){if(Objects.isNull(doubleCheckSingleton)){doubleCheckSingleton = new DoubleCheckSingleton();}}}return doubleCheckSingleton;}
}
-
第一次检查(
if (lazySingleton == null)
)是在同步代码块之外进行的。如果实例已经创建,那么直接返回,无需进入同步代码块,从而提高了性能。 -
第二次检查(
if (lazySingleton == null)
)是在同步代码块内部进行的。这是因为可能会有多个线程同时通过了第一次检查,进入到同步代码块中。通过第二次检查,确保只有一个线程能够创建实例。 -
之所以需要双重检查,是因为如果只进行一次检查(在同步代码块内部),那么每次获取实例都需要进行同步,性能较差。而只进行外部的一次检查,又无法保证线程安全,可能会出现多个线程创建多个实例的情况。
通过双重检查,既保证了线程安全,又在一定程度上提高了性能。
需要注意的是,为了确保线程之间对 lazySingleton
变量的可见性,需要将其声明为 volatile
类型
天上飞的理论,要有落地的实现
光在说性能咋好咋好,谁知道好不好呢
现在请拿起你们的键盘,打开你们的代码编辑器,开始写代码
@Testvoid getThreadSafeLazyInstance() throws InterruptedException {final int threadCount = 100000; // 减少线程数量以减轻资源负担CountDownLatch latch = new CountDownLatch(threadCount);long start = System.currentTimeMillis();for (int i = 0; i < threadCount; i++) {CountDownLatch finalLatch = latch;new Thread(() -> {ThreadSafeLazySingleton instance1 = ThreadSafeLazySingleton.getInstance();ThreadSafeLazySingleton instance2 = ThreadSafeLazySingleton.getInstance();assertEquals(instance1, instance2);finalLatch.countDown(); // 通知CountDownLatch一个线程已经完成}).start();}latch.await(); // 等待所有线程完成long end = System.currentTimeMillis();System.out.println("ThreadSafeLazySingleton: " + (end - start) + "ms");// 重置CountDownLatch并测试第二个单例latch = new CountDownLatch(threadCount);start = System.currentTimeMillis();for (int i = 0; i < threadCount; i++) {CountDownLatch finalLatch1 = latch;new Thread(() -> {DoubleCheckSingleton instance1 = DoubleCheckSingleton.getInstance();DoubleCheckSingleton instance2 = DoubleCheckSingleton.getInstance();assertEquals(instance1, instance2);finalLatch1.countDown();}).start();}latch.await();end = System.currentTimeMillis();System.out.println("DoubleCheckLockingSingleton: " + (end - start) + "ms");}
运行多次,结果均表明:双重检查锁的耗时要低一些,效率高一些
💓饿汉式
饿汉式是在类加载时就创建实例,简单高效,但可能造成资源浪费
这个代码就比较简单了,容易理解
代码清单
/*** @author tiancx* @description: 饿汉式单例模式* @date 2024年07月20日*/
public class EagerSingleton {private static final EagerSingleton EAGER_SINGLETON = new EagerSingleton();private EagerSingleton(){}public static EagerSingleton getInstance(){return EAGER_SINGLETON;}
}
💖枚举实现
Joshua Bloch 大神说过的这么一句话:单元素的枚举类型已经成为实现Singleton的最佳方法
枚举类型在 Java 中本身就保证了实例的唯一性,并且在序列化和反序列化过程中也能保持单例的特性,所以枚举也是实现单例的一种不错的方式
枚举实现单例模式的优点在于
简洁、线程安全,并且能够防止通过反射和序列化破坏单例
代码清单
public enum EnumSingleton {INSTANCE;private String url;private String username;private String password;// 构造函数私有化,防止外部创建实例private EnumSingleton() {//从配置文件中读取数据库连接信息//这里就不写了}
}
我么需要使用时直接EnumSingleton.INSTANCE然后写对应的字段就行了(EnumSingleton.INSTANCE.getUrl()),是不是特别方便吖~
💡 优点:
- 线程安全:枚举类型本身的实现机制保证了在多线程环境下的安全性,无需额外的同步措施。
- 防止反序列化破坏单例:由于枚举类的特殊机制,无法通过反序列化创建新的实例,从而保证了单例的唯一性。
- 简洁直观:实现简单,代码量少,易于理解和维护。
- 避免反射攻击:反射机制也无法破坏枚举单例的唯一性。
🍇容器实现
老板太棒啦,都看到这里来啦,这里也是重磅环节额,看了这个,你将会学会Spring框架IOC的核心原理,这里带你写一个简洁版的IOC,学会了也能吊da面试官额😏😏😏~
这里写的简洁版,写复杂了不好理解
自定义注解-Component-代码清单
/*** @author tiancx* @description: 自定义注解:* @date 2024年07月20日*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
}
UserService-代码清单
/*** @author tiancx* @description: 金钱服务-单例模式* @date 2024年07月20日*/
@Component
@Slf4j
public class MoneyService {/*** 充值*/public void add() {log.info("充值");}/*** 提现*/public void delete() {log.info("提现");}
}
SingletonBeanRegistry-代码清单
/*** @author tiancx* @description: 单例bean注册接口* @date 2024年07月20日*/
public interface SingletonBeanRegistry {Object getSingleton(String beanName);void addSingleton(String beanName, Object singletonObject);
}
DefaultSingletonBeanRegistry - 代码清单
/*** @author tiancx* @description: 单例bean注册* @date 2024年07月20日*/
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();@Overridepublic Object getSingleton(String beanName) {return singletonObjects.get(beanName);}@Overridepublic void addSingleton(String beanName, Object singletonObject) {singletonObjects.put(beanName, singletonObject);}
}
BeanFactory - 代码清单
/*** @author tiancx* @description: Bean工厂* @date 2024年07月20日*/
public interface BeanFactory {/*** 获取bean* @param beanName bean名称* @return bean 对象*/Object getBean(String beanName) throws Exception;/*** 获取bean* @param requiredType bean类型* @return bean 对象* @param <T> bean类型* @throws Exception bean不存在时抛异常*/<T> T getBean(Class<T> requiredType) throws Exception;/*** 是否包含bean* @param name bean名称* @return 是否包含*/boolean containsBean(String name);
}
AbstractBeanFactory - 代码清单
/*** @author tiancx* @description: Bean工厂* @date 2024年07月20日*/
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {@Overridepublic Object getBean(String beanName) throws Exception {return getSingleton(beanName);}@Overridepublic <T> T getBean(Class<T> requiredType) throws Exception {Object bean = getBean(requiredType.getName());return (T) bean;}@Overridepublic boolean containsBean(String name) {return false;}}
AnnotationApplicationContext - 代码清单
/*** @author tiancx* @description: 应用上下文* @date 2024年07月20日*/
public class AnnotationApplicationContext extends AbstractBeanFactory {/*** 扫描需要注册的bean*/public AnnotationApplicationContext(Set<Class<?>> classes) throws InstantiationException, IllegalAccessException {for (Class<?> target : classes) {if (target.isAnnotationPresent(Component.class)) {BeanDefinition definition = new BeanDefinition();definition.setBeanClass(target);definition.setInitMethodName(target.getSimpleName());addSingleton(target.getName(), target.newInstance());}}}
}
测试类
@Testvoid testIoc() throws Exception {//找包下的类String packageName = "cn.xin.learn.design.creational.singleton";AnnotationApplicationContext context = new AnnotationApplicationContext(ClassScanner.scanPackage(packageName));Object bean = context.getBean(UserService.class.getName());Assertions.assertEquals(UserService.class, bean.getClass());UserService service = context.getBean(UserService.class);Assertions.assertEquals(service, bean);}
OK吖,这个容器版本的单例是不是很容易理解吖
这里为啥是单例呢,因为是从hashMap里面取的,只要key相同,取的都是同一个~
OK OK,把这些都给面试官讲完后,面试官一定会被你征服,夸你小可爱涅~
【都看到这了,点点赞点点关注呗,爱你们】😚😚
💬
✨ 正在努力的小叮当~
💖 超级爱分享,分享各种有趣干货!
👩💻 提供:模拟面试 | 简历诊断 | 独家简历模板
🌈 感谢关注,关注了你就是我的超级粉丝啦!
🔒 以下内容仅对你可见~
作者:小叮当撩代码,CSDN后端领域新星创作者 |阿里云专家博主
CSDN个人主页:小叮当撩代码
🔎GZH:哆啦A梦撩代码
🎉欢迎关注🔎点赞👍收藏⭐️留言📝