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

导读:本篇文章讲解 Java实现单例模式(懒汉式和饿汉式),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com


前言

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

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

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

一、何谓单例模式?

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

现实生活中的单例:

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

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

思路如下:

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

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

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

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

三、饿汉单例

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

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

饿汉式单例,天然的线程安全。系统初始化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;
   }
}

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

三个线程并行调用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;
   }
}

但这样同一时间只有一个线程能进入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;
    }
}

为什么使用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实现饿汉式单例和懒汉式单例的所有注意事项,纯手打,希望各位老铁能多多支持,有什么疑问可以私信博主~~~感谢支持

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/115465.html

(0)
Java光头强的头像Java光头强

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!