【深入】java 单例模式

线程安全问题以及单例模式 在这段代码中,Sc.next()是带有阻塞的操作等待用户在控制台输入,notify这里同样也是需要先拿到锁再进行notify(属于java给出的限制),wait操作必须搭配锁来进行因为wait需要释放锁,但是notify操作原则上说不涉及加锁的操作但是java也是强制要求notify搭配synchronized,线程、锁都是操作系统本身支持的特性,wait和notify在操作系统中也有原生的对应的api,原生api中wait必须搭配锁来使用notify则不需要。但是我们不是在单例模式下只能实例一次的吗? 阅读详情
关于单例模式的文章,其实网上早就已经泛滥了。但一个小小的单例,里面却是有着许多的变化。网上的文章大多也是提到了其中的一个或几个点,很少有比较全面且脉络清晰的文章,于是,我便萌生了写这篇文章的念头。企图把这个单例说透,说深入。但愿我不会做的太差。

  首先来看一个典型的实现:

复制代码
 1 /**
 2  * 基础的单例模式,Lazy模式,非线程安全
 3  * 优点:lazy,初次使用时实例化单例,避免资源浪费
 4  * 缺点:1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题
 5  * 2、非线程安全。多线程下可能会有多个实例被初始化。
 6  * 
 7  * @author laichendong
 8  * @since 2011-12-5
 9  */
10 public class SingletonOne {
11     
12     /** 单例实例变量 */
13     private static SingletonOne instance = null;
14     
15     /**
16      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
17*/
18     private SingletonOne() {
19         
20     }
21     
22     /**
23      * 获取单例对象实例
24      * 
25      * @return 单例对象
26*/
27     public static SingletonOne getInstance() {
28         if (instance == null) { // 1
29             instance = new SingletonOne(); // 2
30         }
31         return instance;
32     }
33     
34 }
复制代码

  注释中已经有简单的分析了。接下来分析一下关于“非线程安全”的部分。

  1、当线程A进入到第28行(#1)时,检查instance是否为空,此时是空的。
  2、此时,线程B也进入到28行(#1)。切换到线程B执行。同样检查instance为空,于是往下执行29行(#2),创建了一个实例。接着返回了。
  3、在切换回线程A,由于之前检查到instance为空。所以也会执行29行(#2)创建实例。返回。
  4、至此,已经有两个实例被创建了,这不是我们所希望的。 

 怎么解决线程安全问题?

  方法一:同步方法。即在getInstance()方法上加上synchronized关键字。这时单例变成了  

使用同步方法的单例

 

  加上synchronized后确实实现了线程的互斥访问getInstance()方法。从而保证了线程安全。但是这样就完美了么?我们看。其实在典型实现里,会导致问题的只是当instance还没有被实例化的时候,多个线程访问#1的代码才会导致问题。而当instance已经实例化完成后。每次调用getInstance(),其实都是直接返回的。即使是多个线程访问,也不会出问题。但给方法加上synchronized后。所有getInstance()的调用都要同步了。其实我们只是在第一次调用的时候要同步。而同步需要消耗性能。这就是问题。

  方法二:双重检查加锁Double-checked locking。
  
其实经过分析发现,我们只要保证 instance = new SingletonOne(); 是线程互斥访问的就可以保证线程安全了。那把同步方法加以改造,只用synchronized块包裹这一句。就得到了下面的代码:

复制代码
1     public static SingletonThree getInstance() {
2         if (instance == null) { // 1
3             synchronized (SingletonThree.class) {
4                 instance = new SingletonThree(); // 2
5             }
6         }
7         return instance;
8     }
复制代码

 

  这个方法可行么?分析一下发现是不行的!
  1、线程A和线程B同时进入//1的位置。这时instance是为空的。
  2、线程A进入synchronized块,创建实例,线程B等待。
  3、线程A返回,线程B继续进入synchronized块,创建实例。。。
  4、这时已经有两个实例创建了。 

  为了解决这个问题。我们需要在//2的之前,再加上一次检查instance是否被实例化。(双重检查加锁)接下来,代码变成了这样:

复制代码
 1     public static SingletonThree getInstance() {
 2         if (instance == null) { // 1
 3             synchronized (SingletonThree.class) {
 4                 if (instance == null) { 
 5                     instance = new SingletonThree(); // 2
 6                 }
 7             }
 8         }
 9         return instance;
10     }
复制代码

 

  这样,当线程A返回,线程B进入synchronized块后,会先检查一下instance实例是否被创建,这时实例已经被线程A创建过了。所以线程B不会再创建实例,而是直接返回。貌似!到此为止,这个问题已经被我们完美的解决了。遗憾的是,事实完全不是这样!这个方法在单核和 多核的cpu下都不能保证很好的工作。导致这个方法失败的原因是当前java平台的内存模型。java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制。正是这个机制导致了双重检查加锁方法的失效。这个问题的关键在上面代码上的第5行:instance = new SingletonThree(); 这行其实做了两个事情:1、调用构造方法,创建了一个实例。2、把这个实例赋值给instance这个实例变量。可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,instance已经被设置为非空了。下面我们看一下出问题的过程:
  1、线程A进入getInstance()方法。
  2、因为此时instance为空,所以线程A进入synchronized块。
  3、线程A执行 instance = new SingletonThree(); 把实例变量instance设置成了非空。(注意,实在调用构造方法之前。)
  4、线程A退出,线程B进入。
  5、线程B检查instance是否为空,此时不为空(第三步的时候被线程A设置成了非空)。线程B返回instance的引用。(问题出现了,这时instance的引用并不是SingletonThree的实例,因为没有调用构造方法。) 
  6、线程B退出,线程A进入。
  7、线程A继续调用构造方法,完成instance的初始化,再返回。 

  好吧,继续努力,解决由“无序写”带来的问题。

复制代码
 1     public static SingletonThree getInstance() {
 2         if (instance == null) { 
 3             synchronized (SingletonThree.class) {           // 1
 4                 SingletonThree temp = instance;             // 2
 5                 if (temp == null) {
 6                     synchronized (SingletonThree.class) {   // 3
 7                         temp = new SingletonThree();        // 4
 8                     }
 9                     instance = temp;                        // 5
10                 }
11             }
12         }
13         return instance;
14     }
复制代码

 

  解释一下执行步骤。
  1、线程A进入getInstance()方法。
  2、因为instance是空的 ,所以线程A进入位置//1的第一个synchronized块。
  3、线程A执行位置//2的代码,把instance赋值给本地变量temp。instance为空,所以temp也为空。 
  4、因为temp为空,所以线程A进入位置//3的第二个synchronized块。
  5、线程A执行位置//4的代码,把temp设置成非空,但还没有调用构造方法!(“无序写”问题) 
  6、线程A阻塞,线程B进入getInstance()方法。
  7、因为instance为空,所以线程B试图进入第一个synchronized块。但由于线程A已经在里面了。所以无法进入。线程B阻塞。
  8、线程A激活,继续执行位置//4的代码。调用构造方法。生成实例。
  9、将temp的实例引用赋值给instance。退出两个synchronized块。返回实例。
  10、线程B激活,进入第一个synchronized块。
  11、线程B执行位置//2的代码,把instance实例赋值给temp本地变量。
  12、线程B判断本地变量temp不为空,所以跳过if块。返回instance实例。

  好吧,问题终于解决了,线程安全了。但是我们的代码由最初的3行代码变成了现在的一大坨~。于是又有了下面的方法。

  方法三:预先初始化static变量。

复制代码
 1 /**
 2  * 预先初始化static变量 的单例模式  非Lazy  线程安全
 3  * 优点:
 4  * 1、线程安全 
 5  * 缺点:
 6  * 1、非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。
 7  * 
 8  * @author laichendong
 9  * @since 2011-12-5
10  */
11 public class SingletonFour {
12     
13     /** 单例变量 ,static的,在类加载时进行初始化一次,保证线程安全 */
14     private static SingletonFour instance = new SingletonFour();
15     
16     /**
17      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
18      */
19     private SingletonFour() {
20         
21     }
22     
23     /**
24      * 获取单例对象实例
25      * 
26      * @return 单例对象
27      */
28     public static SingletonFour getInstance() {
29         return instance;
30     }
31     
32 }
复制代码

  看到这个方法,世界又变得清净了。由于java的机制,static的成员变量只在类加载的时候初始化一次,且类加载是线程安全的。所以这个方法实现的单例是线程安全的。但是这个方法却牺牲了Lazy的特性。单例类加载的时候就实例化了。如注释所述:非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。

  那到底有没有完美的办法?懒加载,线程安全,代码简单。

  方法四:使用内部类。

 

复制代码
 1 /**
 2  * 基于内部类的单例模式  Lazy  线程安全
 3  * 优点:
 4  * 1、线程安全
 5  * 2、lazy
 6  * 缺点:
 7  * 1、待发现
 8  * 
 9  * @author laichendong
10  * @since 2011-12-5
11  */
12 public class SingletonFive {
13     
14     /**
15      * 内部类,用于实现lzay机制
16 */
17     private static class SingletonHolder{
18         /** 单例变量  */
19         private static SingletonFive instance = new SingletonFive();
20     }
21     
22     /**
23      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
24 */
25     private SingletonFive() {
26         
27     }
28     
29     /**
30      * 获取单例对象实例
31      * 
32      * @return 单例对象
33 */
34     public static SingletonFive getInstance() {
35         return SingletonHolder.instance;
36     }
37     
38 }
复制代码

  解释一下,因为java机制规定,内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy),而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候实例化一次instance。

 

  最后,总结一下:
  1、如果单例对象不大,允许非懒加载,可以使用方法三。
  2、如果需要懒加载,且允许一部分性能损耗,可以使用方法一。(官方说目前高版本的synchronized已经比较快了)
  3、如果需要懒加载,且不怕麻烦,可以使用方法二。
  4、如果需要懒加载,没有且!推荐使用方法四。

Java多线程--解决单例模式中的懒汉式的线程安全问题 阅读详情

相关推荐

MultiOTP Credential Provider避坑指南:从安装到RDP双因素认证的全流程解析

本文详细解析了MultiOTP Credential Provider从安装到实现RDP双因素认证(MFA)的全流程,重点分享了Docker部署、Active Directory集成及网络级认证(NLA)兼容性等环节的实战避坑经验,旨在帮助运维人员高效、稳定地部署离线双因素认证方案,提升Windows远程桌面安全性。

weixin_29255731的博客 641

Java多线程基础-8:单例模式及其线程安全问题

单例指的就是单个实例(instance),也就是单个对象(对象就是类的实例)。单例模式指的是某个类在进程中只有唯一一个实例(在一个程序中,只能创建一个实例(一个对象),不能创建多个对象)。按理来说,在写代码的时候多 new 几次,就能创建多个对象了。但在语法上,是有办法禁止这样多 new 几次的操作的。也就是说,Java中的单例模式,实际上是借助 Java 语法,保证某个类只能够创建出一个实例,而不能被new多次。为什么会有这样的用途?其实原因是很简单的:在有些场景下,本身它就要求某个概念是单例的。

wyd_333的博客 3315

使用python读取卫星测高数据并做相应的处理

5.在python处理结果如下,最大的优点就是比matlab快30-40倍。4.进行粗差的如NaN值,还有进行3倍方差进行对粗差处理。1.首先遍历卫星数据的文件,挨个读取每个数据。有问题,请各位读者及时告知,一起完善内容。3.进行下一步,筛选在研究区域的数据。4.进行按天求平均和按月求平均。2.读取数据主体代码。

爱下去更难! 1131

线程安全之单例模式

这篇文章,我们会介绍一下单例模式,但这里的单例模式,不是我们所说的设计模式,当然听到设计模式,大家一定都说,我当然知道设计模式了,有23种呢?一下子一顿输出,当然我这里说的单例模式还是跟设计模式有一些区别的,当然我不做概述,因为我也没咋个去了解过设计模式,我把大家拉回来,什么是多线程对的单例模式呢?看完我以下的解释相信你会明白的.多线程环境下的单例模式需要保证只有一个实例对象被创建,并且可以在多线程环境下安全地访问该实例。

m0_61869253的博客 1690

SAP Fiori单点登录避坑指南:从SAML原理到实战,解决‘第一次登录跳转主页失败’和‘偶发进不了单点’

本文深入解析SAP Fiori单点登录(SSO)的SAML实战与疑难排查,涵盖SAML协议核心机制、SICF登录优先级控制及安全诊断工具的高级用法。针对‘首次登录跳转主页失败’和‘偶发单点登录失效’问题,提供从配置优化到生产环境稳定运行的全面解决方案,助力企业高效实现数字化转型。

weixin_29327977的博客 405

Marin说PCB之LPDDR5的布线注意事项--01

以上就是本期所有内容了,有些是通用的LPDDR5的设计准则,有的是针对单板的设计规则,严格意义说是没有哪一种规则是完全适用的LPDDR5的 通用规则的,不同的单板可能板厚,板材,层数,SOC以及DDR的型号都是不同的,一般的做法就是严格按照SOC手册上的LPDDR5的要求加上仿真同事给的建议修改优化就好了,我们下期文章不见不散。我们知道想要做好一个LPDDR颗粒,需要考虑的是因素有很多例如,电源完整性,信号完整性,层叠的设计,布局的摆放,布线的合理规划,去耦电容的摆放等等。

weixin_45223454的博客 3161

Java中单例模式的线程安全性

Java中单例模式的线程安全性

QYgujingjing的专栏 1211

Keras中神经网络可视化模块keras.utils.visualize_util安装配置方法

Keras中神经网络可视化模块keras.utils.visualize_util安装配置方法

jacke121的专栏 2759

Java 单例模式(Singleton Pattern)

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式的核心是:限制类的实例化次数为 1,并提供全局访问点来获取该实例。单例模式通常用于那些需要共享资源或提供全局访问的类,比如配置管理类、数据库连接池等。

qq_35082497的博客 1127

今日头条面试——iOS开发面试题

layoutIfNeeded和setNeedsLayout的区别。* Pod update和pod install的区别。* 10、autoreleasepool的使用场景。* 如何hook一个对象的方法,而不影响其它对象。* 数组cop后里面的元素会复制一份新的吗。* 介绍项目,主要介绍自己强项一点的地方。* assing可以使用在对象中吗。* isEquel和hash的关系。* 4、谈谈runtime的理解。* 8、谈谈runloop的理解。* copy和strong的区别。

FPGA/MATLAB学习教程/源码/项目合作开发 593

lammps案例:Cu三点弯曲模拟

大家好,我是小马老师。 本文分享一个Cu弯曲的案例。 本案例参考三点弯曲实验,三点弯曲试验是将试样放在弯曲装置上,在试样上加载进行弯曲试验,直到达到规定的弯曲程度或发生断裂。 模拟原理是在z方向固定两侧端点,在x和y方向不固定,模拟左右两个支撑点,中间压块向下运动实现弯曲。 初始结构: 运行结果为: 模拟的重点是使用fix setforce命令压住左右两端,使其不能沿z轴上下移动,但是又不能在xy方向限制其移动。 压块的运动方式比较简单,给其一个下压的速度即可。 为防止压块变形,使用setforce命

lammps_jiayou的博客 2276

详解Java设计模式之单例模式(Singleton Pattern)

单例模式只包含一个 Singleton(单例角色)类,在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以使用它的唯一实例。

Elegance never goes out of fashion 2745

VS Code下载安装与使用教程

VSCode下载安装与使用教程

weixin_46713695的博客 4万+

单例模式(Singleton Pattern)在JAVA中的应用

getInstance` 方法提供了一个全局访问点,它通过双重检查锁定模式确保线程安全,并创建类的唯一实例。单例模式是一种简单而强大的设计模式,它通过确保一个类只有一个实例,并提供一个全局访问点,来解决对象创建和访问的问题。本文将介绍单例模式,这是一种常用的创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点。// 输出 true。- **单例类(Singleton)**:这个类负责创建自己的实例,并提供一个全局访问点。- **全局访问点**:提供了一个统一的访问点,方便对实例的管理和访问。

无极低码 1064

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Java Singleton Pattern 单例模式

Java Singleton Pattern 单例模式 维基百科: 点击打开链接 在应用这个模式时,单例对象的类必须保证只有一个实例存在. 关键点: 1.私有构造函数 - 其他类不能实例化一个新的对象。 2.私有化引用 - 不能进行外部修改。 3.公有静态方法是唯一可以获得对象的方式。 public class SingleObject { private

xiaowanggedege的专栏 2111

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Java编写Singleton Pattern 单例模式

Java编写Singleton Pattern 单例模式,以及单例模式的优点和适用场景

weixin_44853907的博客 227

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

java单例模式(Singleton pattern)

<br />java单例模式(Singleton pattern)<br /> <br />转自:http://www.javaeye.com/topic/652617<br />一、定义<br />单例模式(Singleton pattern):确保一个类只有一个实例,并提供一个全局的访问点。<br />这个定义包含两层意思:<br />第一:我们把某个类设计成自己管理的一个单独实例,同时也要避免其他类再自行产生实例。要想取得单个实例,通过单例类是唯一的途径。<br />第二:我们必需提供对这个实例的全局

Warlen的专栏 704

java模式之单例模式(Singleton Pattern)

  java模式之单例模式:          单例模式确保一个类只有一个实例,自行提供这个实例并向整个系统提供这个实例。 特点:       1,一个类只能有一个实例       2,自己创建这个实例       3,整个系统都要使用这个实例    例: 在下面的对象图中,有一个"单例对象",而"客户甲"、"客户乙" 和"客户丙"是单例对象的三个客户对象。可以看到,所

imnol的专栏 850

java单例模式——详解JAVA单例模式及8种实现方式

本文转载自:java单例模式——详解JAVA单例模式及8种实现方式_EatingSoilLang的博客-优快云博客_java单例模式 ## 单例模式是最简单也是最基础的设计模式之一,下边一起学习一下单例模式! 一.单例模式的定义: 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler

chali1314的博客 2万+

Java五种单例模式

Java的五种单例模式,大家应该都接触过或者都用过。本篇介绍下Java的五种单例模式,并分析其优缺点,希望给初学者提供一点帮助。

zhaojun的博客 2万+

Java 单例模式

饿汉式:在类加载时创建实例,简单易懂,无需加锁。懒汉式:延迟创建实例,需考虑线程安全问题。枚举:简洁且天然具备线程安全性和序列化安全性,防止反射破坏。

Ljj_fishhG的博客 1716

Java之单例模式

在多线程环境下程序运行的结果与单线程环境下程序运行的结果不一样(不达预期)单例模式是一种设计模式(设计模式:就是在特定的场景下,解决问题最优的方式,类似于棋谱),单例:顾名思义,全局只有一个实例对象。初始状态instance=null,外层if判断为true,进入,此时三个线程参与锁竞争,线程1拿到了锁,线程2和线程3处在堵塞状态,线程1内层if判断为true,执行instance的赋值操作,之后释放锁资源,返回赋值之后的对象,线程2拿到了锁资源,内层if判断为false,释放锁资源,返回instance,

qq_64580912的博客 1967

Singleton Pattern单例模式-java

单例模式(Singleton) 单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处: 1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。 2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。 3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的...

u010609453的博客 196

java单例模式、Scala单例模式

单例java的两种单例模式Scala的单例模式 java的两种单例模式 /** * 单例 * * 一个类只能创建一个对象, * 定义的步骤: * 恶汉式: * 1、私有构造器 * 2、提供一个public的static的返回值为本类引用的方法 * 3、为了给第2步中提供实例,创建一个private的s...

Baiyabai的博客 1235
上一篇: Linux zip
下一篇: AFNetworking2.0源码解析
focusjava
博客等级 码龄14年 127粉丝 528原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值