栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

你还是只会饿汉式吗?——三种单例模式写法

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

你还是只会饿汉式吗?——三种单例模式写法

文章目录
  • 引言
  • 单例模式介绍
  • 饿汉式
  • 懒汉式
  • 结语

引言

还记得以前在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是面试必考的知识点,可能不一定要你现场手撕,但是只要问到单例模式,一定会让你说出其实现细节,我们先来看看代码实现:

    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 两个实例我们就能获取到两个不一样的instance
    		DoubleCheckedLockingTest o1 = new DoubleCheckedLockingTest();
    		DoubleCheckedLockingTest o2 = new DoubleCheckedLockingTest();
    	}
    }
    
结语

看到这里,相比同学们对单例模式已经有了初步的了解,事实上Spring框架大量使用了这一设计模式,在后续的学习中,可以尝试阅读Spring源码来深刻体会单例的优势。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/423901.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号