【JavaEE】单例模式如何保证在多线程环境下线程安全高可用?
创始人
2024-05-12 10:35:01
0

文章目录

  • 1 单例模式回顾
  • 2 饿汉式单例模式的实现
  • 3 懒汉式单例模式的实现
  • 4 单例模式的线程安全问题分析
  • 5 线程安全的懒汉式实现
  • 6 总结


1 单例模式回顾

 单例模式是设计模式的一种。而设计模式就是针对我们实际开发中写代码所遇到的不同场景所设立的解决方案。在笔者JavaSE阶段的文章中,我们曾经聊到过单例模式,这里给出文章链接,有兴趣的读者可以自行阅读:单例模式懒汉式与饿汉式的实现 ,在本文中,笔者也会进行必要的总结。下面,进入正文:
在这里插入图片描述

 所谓类的单例设计模式,就是采用一定的方法保证在整个软件系统中,对某一个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。常见的实现方式有两种,懒汉式和饿汉式。

  这里的懒汉和饿汉可以这样理解,对于饿汉来说,肚子很饿,是比较着急去吃饭的,体现出一个着急的态度;而对于懒汉来说,在计算机中,这属于褒义词,指的是“不着急”,只有当你需要的时候,我才给你提供需要的东西,反而更加高效。 所以说:

  1. 懒汉式的单例模式,是比较着急去创建对象实例的;
  2. 饿汉式的单例模式,不太着急去创建对象实例,只有在需要的时候,才去创建。

 现在,饿汉式和懒汉式的区别我们了解清楚了,无非就是创建对象的时机不同。 但是,无论是哪种实现方式,都需要满足一个大前提:对于该类,只能存在一个对象实例。

 因此,为了做到这点,我们可以考虑将构造器私有化,这样在外部就没有办法手动创建对象。同时,在类中,我们需要手动创建一个对象,并对外提供一个公有的方法,使得该创建的对象,能够被外部所获取。

 下面我们来具体聊一聊,饿汉式与懒汉式该如何实现单例模式。
在这里插入图片描述


2 饿汉式单例模式的实现

对于饿汉式的实现,主要步骤如下:(饿汉主要体现在直接创建对象)

  1. 先将构造器私有化, 避免用户直接在外部 new 新对象。
  2. 在类的内部属性部分直接创建一个对象(我们已经将构造器私有化了,因此在外部没有办法 new 新对象,所以只能在类的内部创建对象)。
  3. 向外提供一个公共的静态方法,用于外部引用该对象。(创建好了对象后,如果对外不提供公共方法,我们就没有办法使用这个被创建的对象。)

这里有一点值得说明,静态?为什么需要使用static去修饰对象和方法呢?

在类的外部,我们无法使用 new 新建对象,也就是说,我们只能通过 类名.xxx 的方式来返回创建的对象,因此,获取对象的对外公共方法需要使用 static 关键字修饰。而该方法中,需要返回类中创建的对象,静态方法只能使用静态变量,因此,对象属性也应当设置为 static。

实现代码如下:

/*** @author 兴趣使然黄小黄* @version 1.0* 单例模式的饿汉式实现*/
@SuppressWarnings({"all"})
public class Singleton {// 提供一个对象private static Singleton instance = new Singleton();// 防止外部new对象private Singleton(){}// 使得外部能够访问到创建的对象public static Singleton getInstance(){return instance;}
}

3 懒汉式单例模式的实现

对于懒汉式的实现,主要步骤如下:(懒汉主要体现在不直接创建对象,而是在需要的时候再创建)

  1. 先将构造器私有化, 避免用户直接在外部 new 新对象。
  2. 在类的内部 声明一个对象引用,但是不创建, 即先不 new;
  3. 向外提供一个公共的静态方法,用于创建对象并返回对象引用。 需要在该方法中创建对象,并返回给外部。

实现代码如下:

/*** @author 兴趣使然黄小黄* @version 1.0* 单例模式的懒汉式实现*/
@SuppressWarnings({"all"})
public class Singleton2 {// 提供一个对象的引用private static Singleton2 instance = null;// 防止外部new对象private Singleton2(){}// 用于创建对象或使得外部能够访问到创建的对象public static Singleton2 getInstance(){if (instance == null){instance = new Singleton2();}return instance;}
}

4 单例模式的线程安全问题分析

以上,我们成功实现了饿汉式与懒汉式的单例模式。在串行、单线程环境中,饿汉式和懒汉式实现的单例模式都没有什么问题。那么,如果是在多线程环境呢?这样就存在了一些线程安全问题。

什么是线程安全问题?

线程安不安全,指的是在多线程环境中,代码是否会产生bug。如果,在多线程环境中,不会产生bug,那么我们就说,这是线程安全的;如果,在多线程环境中,会产生bug,我们就说这样的实现方式是不安全的。

什么样的情况下会导致线程安全问题呢?

如果多个线程,只是读取同一个变量、资源,而不作修改,此时仍然是线程安全的。如果,存在修改,则可能会出现线程安全问题。

对于饿汉式单例模式的实现,由于其仅仅只是读取了变量的内容,因此,是线程安全的!
在这里插入图片描述

而懒汉式单例模式的实现,就不一样了。在懒汉模式中,既包含了读,又包含了写,且读写还不是原子的(分成了两个步骤~), 所以存在线程安全问题。

比如:两个进程g1,g2同时进入该方法,需要返回给 g1 ,g2 对象实例。当 g1 进入该方法后,进行判断,不为 null,因此创建一个对象,而在创建对象的过程中,g2 刚好也进入了该方法,也被判断成了不为 null,此时,g1, g2就指向不同的对象了,所以它是线程不安全的。

在这里插入图片描述
可能文字叙述,还是有些抽象,下面,对例子进行画图描述,以时间为顺序,同样,进程还是用 g1,g2来表示:
在这里插入图片描述
由于进程调度的时机是不确定的,以上只是列举了一种可能导致线程安全问题的调度方式。

那么如何保证懒汉式单例模式的实现是线程安全的呢?

在这里插入图片描述


5 线程安全的懒汉式实现

如何实现线程安全?
很简单!既然你说读和写分成了两个步骤,会出现线程安全问题,那我 加锁 不就完了!
在这里插入图片描述
嗯,很好,这样就保证了,在进行读和写操作的时候,只有一个线程在操作,就可以避免另一个线程同时进入 if 语句去创建对象了!

可是,这样真的好吗?

我们简单分析一下,对于懒汉式的实现,是何时会导致线程不安全?

对于懒汉式的代码来说,线程不安全是发生在 instance 被初始化之前!!!在未初始化的时候,即 instance == null,多个线程去调用 getInstance 可能就同时涉及到读和改的问题,引发线程安全问题。
但是,一旦 instance 不是null,已经被初始化后,getInstance方法就只剩下读操作了,此时一定是线程安全的。

那么,我们刚刚的加锁方式,就有了些问题:无论是 instance 初始化之前,还是 instance 初始化之后,都会导致阻塞,存在大量的锁竞争!

而我们知道,对于加锁操作来说,加锁确实可以让代码保证线程安全,但是也会付出相应的代价, 最典型的代价就是程序运行变慢了。本来我多个线程运行的好好的,你给我加了把锁,其他线程不就进不去了吗?(一定程度上的阻塞)

因此,我们需要做出如下改进:再加一个判断!判断 instance 是否被初始化,如果已经初始化,就不必进入加锁的代码块了!
在这里插入图片描述
但是!这样还会有问题,即 内存可见性 问题。

如果多个线程,都去调用 getInstance方法,就会造成大量的读 instance 操作。而,我们知道,对于在内存读和在寄存器读来说,后者的效率更高。因此,编译器会自作主张,对代码进行优化:将读内存操作优化成读寄存器操作。
也就是说,即使某个线程将instance初始化了,但是由于编译器的优化,在其他线程的视角看起来,instance 还是null。

也就是说,这里的内存可见性问题,会导致第一个if语句失效,即不该加锁的时候还会加锁。因此,我们需要保证内存可见!即使用 volatile 对 instance 进行修饰。
在这里插入图片描述

完整代码如下:

/*** @author 兴趣使然黄小黄* @version 1.0* 单例模式的懒汉式实现* 保证线程安全*/
@SuppressWarnings({"all"})
public class Singleton2 {// 提供一个对象的引用private static volatile Singleton2 instance = null;// 防止外部new对象private Singleton2(){}// 用于创建对象或使得外部能够访问到创建的对象public static Singleton2 getInstance(){if (instance == null){synchronized(Singleton2.class){if (instance == null){instance = new Singleton2();}}}return instance;}
}

6 总结

 对于饿汉式单例模式,由于只存在对同一变量的读操作,不会导致线程安全问题。

对于懒汉式单例模式,实现线程安全主要有如下三步:

  1. 在正确的位置加锁!
  2. 双重 if 判定!
  3. volatile!

 以上便是本文的全部内容啦!创作不易,如果你有任何问题,欢迎私信,感谢您的支持!

在这里插入图片描述

相关内容

热门资讯

六一主持词开场白 六一主持词开场白  送去五月的芬芳,迎来六月的时光,六一儿童节即将来临,以下是由应届毕业生网PQ小编...
郭德纲相声论梦台词 郭德纲相声论梦台词  郭德纲出现引起了社会对相声关注,很多人对相声感兴趣,开始思考相声中问题。下面是...
学校元旦经典致辞 学校元旦经典致辞范文(精选5篇)  在我们平凡的日常里,大家一定都接触过致辞吧,致辞的措词造句要考虑...
快板台词 快板台词大全  快板是一种汉族说唱艺术,属于中国曲艺韵诵类曲种。早年称作“数来宝”,也叫称“顺口溜”...
读书活动主持词开场白   读书活动主持词开场白1  尊敬的各位领导、各位来宾、各位参赛选手:  大家下午好!在春意盎然,百...
感谢老师致辞 感谢老师致辞(精选12篇)  在平日的学习、工作和生活里,要用到致辞的地方还是很多的,致辞是指在举行...
结婚典礼感谢致辞 结婚典礼感谢致辞(通用15篇)  在平平淡淡的学习、工作、生活中,要用到致辞的情况还是蛮多的,致辞要...
大鱼海棠配音台词 大鱼海棠配音台词  蛰伏十二年的动画《大鱼海棠》,终于上映了。虽然剧情被很多人吐槽,但美得不像话的画...
小学毕业典礼主持词 小学毕业典礼主持词实用范文  活动对象的不同,主持词的写作风格也会大不一样。在当今中国社会,司仪等是...
篮球赛闭幕词 篮球赛闭幕词(精选15篇)篮球赛闭幕词1各位来宾、同志们:  为期xx天的xxx第二届职工篮球赛,在...
元旦联欢晚会主持词 元旦联欢晚会主持词范文(精选9篇)  主持词分为会议主持词、晚会主持词、活动主持词、婚庆主持词等。在...
大型文艺演出主持词 大型文艺演出主持词  主持词可以采用和历史文化有关的表述方法去写作以提升活动的文化内涵。在当今中国社...
开学典礼致辞 开学典礼致辞15篇  在日常生活或是工作学习中,大家都经常接触到致辞吧,致辞具有“礼仪性”或“仪式化...
农村婚礼司仪主持词 农村婚礼司仪主持词15篇  主持词需要富有情感,充满热情,才能有效地吸引到观众。在当今不断发展的世界...
如何主持会议参考 如何主持会议范文参考  1.宣布会议开始。会议时间到后,若全体与会人员都已到位(至少重要的与会人员已...
韩剧漂亮男人经典台词 韩剧漂亮男人经典台词  1、答案都是在当事人手里的,不知道人们为什么,明明是自己非常了解的问题,但是...
外公七十大寿祝寿词 外公七十大寿祝寿词  外公七十大寿祝寿词  尊敬的外公、外婆、各位长辈、各位来宾:    大家好!今...
《香瓜七兄弟》经典台词 《香瓜七兄弟》经典台词  1. 阿丽呀,我哪好,我改还不行吗  2. 记不记得上回你尿炕 阿姨来了你...
孙子满月酒爷爷致辞 孙子满月酒爷爷致辞(通用6篇)  无论是身处学校还是步入社会,大家都不可避免地会接触到致辞吧,致辞具...
国庆文艺汇演主持词 国庆文艺汇演主持词  主持词要注意活动对象,针对活动对象写相应的主持词。在当今社会中,主持人在活动中...