单例模式的三种写法
文章目录
- 引言
- 单例模式介绍
- 饿汉式
- 懒汉式
- 结语
引言
还记得以前在B站看一些Java入门教学视频的时候,上课的老师说如果面试官问你单例模式,一定要写饿汉式,因为线程安全,如果写懒汉式肯定要问你怎么解决线程安全问题。当初还觉得非常有道理,现在回想起来,***,真坑!本文就来介绍一下目前市面上最流行的三种单例写法。
单例模式介绍
单例模式是一种创建型模式,顾名思义,使用这种模式只会产生一个实例。具体来说,就是类提供了某个对象的创建接口,这个接口内部的一些细节能够保证每次都返回相同的实例。下面来看下具体如何实现这一模式。
饿汉式
饿汉式实现非常简单,因为它巧妙的利用了类加载机制实现了线程安全,即在初始化类变量阶段创捷了那个唯一实例,而在方法层面直接返回已创建的实例即可。但这样做的坏处是浪费内存,很可能这个实例一直得不到使用,但内存却实实在在被占用了。
/*** 单例模式:饿汉式*/
public class EagerSingleton {public static SingleLinkedList instance = new SingleLinkedList();private EagerSingleton() {}/*** 获取实例*/public static SingleLinkedList getInstance() {return instance;}}
懒汉式
懒汉式的实现有三种方式:线程安全以及双重校验式
-
线程安全
线程安全版本的写法特点是:
1. 没有在类加载过程中直接 new 出实例
2. 在获取实例的接口上添加了方法锁,并在方法体内部进行实例创建判断,保证不重复创建实例
相比饿汉式,这种写法可以节省内存,但同样因为方法锁的存在会影响性能/*** 单例模式:懒汉式*/ public class LazySingleton {public static SingleLinkedList instance;private LazySingleton() {}/*** 获取实例*/public synchronized static SingleLinkedList getInstance() {if (instance != null) {instance = new SingleLinkedList();}return instance;}} -
双重校验锁
毫不夸张地说,DCL是面试必考的知识点,可能不一定要你现场手撕,但是只要问到单例模式,一定会让你说出其实现细节,我们先来看看代码实现:/*** 单例模式:双重校验锁(DCL,double checked locking)*/ public class DoubleCheckedLockingTest {public volatile static DoubleCheckedLockingTest instance;// 通过这种私有化构造器的方式可以防止外部获取接口private DoubleCheckedLockingTest(){}/*** 获取实例*/public static DoubleCheckedLockingTest getInstance() {// 若已创建实例则直接返回if (instance == null) {// 加锁保证创建实例过程的线程安全synchronized (DoubleCheckedLockingTest.class) {// 若不进行判空,拿到锁后直接创建实例可能会存在以下情况// 多个线程同时通过了第一次检查,但只要有一个线程成功创建,其他线程再创建就违背了单例的原则if (instance == null) {instance = new DoubleCheckedLockingTest();}}}return instance;}}首先,DCL的instance初始化(
public volatile static DoubleCheckedLockingTest instance;)就和其他写法不一样,细心的同学会发现这里多了一个 volatile 关键字,加这个关键字的原因和指令重排序有关。补充知识点:对象创建过程主要分为三步:1.分配内存空间;2.初始化对象;3.将对象指向刚分配的内存空间
在对象创建过程中,分配完内存空间后,就会进行初始化对象,然后将对象指向刚分配的内存空间。这两部操作调换顺序一般情况下是不影响对象创建的,但如果因为指令重排序而调换了执行顺序,那么在多线程环境下,可能会造成某个线程访问到一个未完全初始化的对象。
其次,DCL没有采用懒汉式的方法锁,而是通过锁代码块的形式进行细粒度控。
最后,解释一下为什么构造器前面要是有 private 权限,如果我们使用public,那么每次 new 一个类的实例都会创建一个新的instance,这样就起不到单例的效果,因此必须私有化构造器
public Main {public static void main(String[] args) {// 如果不私有化构造器,new 两个实例我们就能获取到两个不一样的instanceDoubleCheckedLockingTest o1 = new DoubleCheckedLockingTest();DoubleCheckedLockingTest o2 = new DoubleCheckedLockingTest();} }
结语
看到这里,相比同学们对单例模式已经有了初步的了解,事实上Spring框架大量使用了这一设计模式,在后续的学习中,可以尝试阅读Spring源码来深刻体会单例的优势。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
