• Java实现单例模式(懒汉式和饿汉式)



    前言

    博主个人社区:开发与算法学习社区

    博主个人主页:Killing Vibe的博客

    欢迎大家加入,一起交流学习~~

    一、何谓单例模式?

    所谓的单例模式保证某个类在程序中有且只有一个对象。

    现实生活中的单例:

    一个类只有一个对象,地球类-地球这一个对象,太阳类-太阳这一个对象。

    二、如何控制某个类只有一个对象?

    思路如下:

    1.要创建类的对象,通过构造方法对象

    2.构造方法若是public权限,对于类的外部,就能随意创建对象,无法控制对象个数

    3.所以我们考虑把构造方法私有化,类的外部就彻底没法产生对象了

    构造方法私有化之后,对于类的外部而言就一个对象都没有,如何构造这唯一的对象(私有化的构造方法只能在类的内部调用),只调用一次构造方法即可。

    三、饿汉单例

    有了上述的思路,就可以写出饿汉式单例了:

    public class SingleTon {
        // 惟一的这一个对象
        private static SingleTon singleTon = new SingleTon();
        private SingleTon() {}
        // 调用此方法时,singleTon对象已经产生过了,多线程场景下取回的是同一个单例对象
        public static SingleTon getSingleton() {
            return singleTon;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    饿汉式单例,天然的线程安全。系统初始化JVM加载类的过程中就创建了这个唯一的对象。

    在SingleTon类的外部访问这个唯一的对象,直接通过getSingleTon方法获取这个唯一对象。

    总结一下就三步走:

    1. 构造方法私有化(保证对象的产生个数)
    2. 单例类的内部提供这个唯一的对象(static)
    3. 单例类提供返回这个唯一的对象的静态方法供外部使用

    四、懒汉单例

    只有第一次调用getSingleTon方法,表示外部需要获取这个单例对象时才产生对象。

    系统初始化时,外部不需要这个单例对象,就先不产生,只有当外部需要此对象才实例化对象。这种操作称之为懒加载~

    举个栗子:

    哈希表的构造就是懒加载,构造方法只设置了负载因子。

    在这里插入图片描述

    只有在需要给map中添加元素的时候,表示此时需要table数组,才初始化数组为16。

    在这里插入图片描述

    4.1 单线程下

    类加载的时候不创建实例. 第一次使用的时候才创建实例.

    class LazySingleton {
        private static LazySingleton singleTon;
        private LazySingleton() {}
        public static LazySingleton getSingle() {
            if (singleTon == null) {
                singleTon = new LazySingleton();
           }
            return singleTon;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    但这样的代码在多线程场景下会出现问题,不能保证只有一个对象产生。

    三个线程并行调用getSingle()方法,此时singleTon三个线程看到的就都是null,每个线程都创建了一个对象。

    4.2 多线程下(简单版)

    此时加上 synchronized 可以改善这里的线程安全问题. (把方法锁了)

    class LazySingleton {
        private static LazySingleton singleTon;
        private LazySingleton() {}
        public synchronized static LazySingleton getSingle() {
            if (singleTon == null) {
                singleTon = new LazySingleton();
           }
            return singleTon;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    但这样同一时间只有一个线程能进入getSingle方法,此时这个方法内部都是单线程操作,其他线程要进入此方法都需要获取锁(锁的粒度太低)

    4.3 多线程下(增强版)

    以下代码在加锁的基础上, 做出了进一步改动:

    • 使用双重 if 判定(double-check), 降低锁竞争的频率.
    • 给 instance 加上了 volatile.
    public class LazySingleTon {
        private static volatile LazySingleTon singleTon;
    
        private LazySingleTon() {}
        // 第一次调用获取单例对象方法时才实例化对象
        public static LazySingleTon getSingleTon() {
            if (singleTon == null) {
                // 初始化对象
                synchronized (LazySingleTon.class) {
                    if (singleTon == null) {
                        singleTon = new LazySingleTon();
                    }
                }
            }
            return singleTon;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    为什么使用Double-check?

    1.使用Double-check的原因就是要降低锁的粒度,以上代码只是单例中最核心的代码,单例模式还有很多其他操作,为了保证其他操作尽可能并发执行,需要往小了“锁”。

    2.还需要用第二个if判断的原因就是因为,在同步代码块内部需要再次坚持singleTon是否为空,防止其他线程恢复执行后多次创建了单例对象。

    在这里插入图片描述

    为什么使用Volatile关键字?

    双重加锁,使用volatile关键字保证单例对象的初始化不被中断

    举个栗子:

    假如构造懒汉单例的时候需要初始化 x,y,z 三个变量, 多个线程开始同时运行:

    在这里插入图片描述

    1. 当线程t1执行new操作时,还没完全结束,此时 SingleTon !=null
    2. 对于刚开始执行代码的t2线程来说,它看到singleTon != null 就直接返回了,但是返回的单例对象是一个尚未完全初始化的对象(比如z没来得及初始化为30,可能t2线程的对象z = 0)
    3. 此时若采用volatile修饰单例对象,由于volatile可以保证可见性,new这个操作就会像有一堵墙(内存屏障),其他线程要执行到return操作,JVM一个保证new操作完全结束后才能执行return语句。

    总结

    以上就是多线程场景下用Java实现饿汉式单例和懒汉式单例的所有注意事项,纯手打,希望各位老铁能多多支持,有什么疑问可以私信博主~~~感谢支持

  • 相关阅读:
    我的周刊(第052期)
    java-代码操作服务器之SSH连续发送命令
    【洛谷P2258】子矩阵【DFS+DP】
    第二章 JAVA基础语法
    MarkText快捷键(随时补充中)
    第三十节——组合式API组件传值
    jQuery 入门-----第二节:jQuery 常用API
    【排序算法】堆排序详解与实现
    RocketMQ 关于 No route info of this topic 问题
    【vue2+avue】实现文件上传 技术栈用的是kkfileview
  • 原文地址:https://blog.csdn.net/qq_43575801/article/details/127709635