单例模式可以使得一个类只有一个对象实例,能够减少频繁创建对象的时间和空间开销。单线程模式下一个典型的单例模式代码如下:

 class Singleton{
private static Singleton singleton;
private Singleton(){} public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton(); //1
}
return singleton;
}
}

构造器私有使得外界无法通过构造器实例化Singleton类,要取得实例只能通过getInstance()方法。这是一个延迟加载的版本,即在需要对象的时候才进行实例化操作。该方法在单线程下能够正常运行,但是在多线程环境下会出现由于没有同步措施而导致产生多个单例对象的情况。原因在于可能同时有两个线程A和B同时执行到 if 条件判断语句,A判断singleton为空准备执行//1时让出了CPU时间片,B也判断singleton为空,接着执行//1,此时创建了一个实例对象;A获取了CPU时间片后接着执行//1,也创建了实例对象,这就导致多个单例对象的情况。

解决问题的方法也很简单,使用synchronized关键字:

 class Singleton{
private static Singleton singleton;
private Singleton(){} public static synchronized Singleton getInstance(){
if(singleton == null){
singleton = new Singleton(); //1
}
return singleton;
}
}

这样解决了多线程并发的问题,但是却带来了效率问题:我们的目的是只创建一个实例,即//1处代码只会执行一次,也正是这个地方才需要同步,后面创建了实例之后,singleton非空就会直接返回对象引用,而不用每次都在同步代码块中进行非空验证。那么可以考虑只对//1处进行同步:

 class Singleton{
private static Singleton singleton;
private Singleton(){} public static Singleton getInstance(){
if(singleton == null){
synchronized(Singleton.class){
singleton = new Singleton(); //1
}
}
return singleton;
}
}

这样会带来与第一种一样的问题,即多个线程同时执行到条件判断语句时,会创建多个实例。问题在于当一个线程创建一个实例之后,singleton就不再为空了,但是后续的线程并没有做第二次非空检查。那么很明显,在同步代码块中应该再次做检查,也就是所谓的双重检测:

④双重检测:

 class Singleton{
private static Singleton singleton;
private Singleton(){} public static Singleton getInstance(){
if(singleton == null){
synchronized(Singleton.class){
if(singleton == null)
singleton = new Singleton(); //1
}
}
return singleton;
}
}

到这里已经很完美了,看起来没有问题。但是这种双重检测机制在JDK1.5之前是有问题的,问题还是出在//1,由所谓的无序写入造成的。一般来讲,当初始化一个对象的时候,会经历内存分配、初始化、返回对象在堆上的引用等一系列操作,这种方式产生的对象是一个完整的对象,可以正常使用。但是JAVA的无序写入可能会造成顺序的颠倒,即内存分配、返回对象引用、初始化的顺序,这种情况下对应到//1就是singleton已经不是null,而是指向了堆上的一个对象,但是该对象却还没有完成初始化动作。当后续的线程发现singleton不是null而直接使用的时候,就会出现意料之外的问题。

JDK1.5之后,可以使用volatile关键字修饰变量来解决无序写入产生的问题,因为volatile关键字的一个重要作用是禁止指令重排序,即保证不会出现内存分配、返回对象引用、初始化这样的顺序,从而使得双重检测真正发挥作用。

当然,也可以选择不使用双重检测,而采用非延迟加载的方式来达到相同的效果:

 class Singleton{
private static Singleton singleton = new Singleton();
private Singleton(){} public static Singleton getInstance(){
return singleton;
}
}

【参考】

Java单例模式中双重检查锁的问题

单例模式与双重检测

Java 中的双重检查(Double-Check)

Java并发编程:volatile关键字解析

Java并发笔记——单例与双重检测的更多相关文章

  1. Java学习笔记——单例设计模式Singleton

    单例设计模式:singleton 解决的问题: 确保程序在运行过程中,某个类的实例instance只有一份. 特点: 1 构造函数私有化 2 自己内部声明自己 3 提供一个public方法,负责实例化 ...

  2. Java并发-懒汉式单例设计模式加volatile的原因

    懒汉式单例的double check.例一: class SingletonClass{ private static SingletonClass instance = null; private ...

  3. 转载:java基础之单例

    转载:https://blog.csdn.net/goodlixueyong/article/details/51935526 https://www.cnblogs.com/cielosun/p/6 ...

  4. Java设计模式之单例

    一.Java中的单例: 特点: ① 单例类只有一个实例 ② 单例类必须自己创建自己唯一实例 ③ 单例类必须给所有其他对象提供这一实例 二.两种模式: ①懒汉式单例<线程不安全> 在类加载时 ...

  5. Java复习11. 单例编程

    Java复习11. 单例编程 1.最简单的写法,那个方式是线程不安全的 public class Singleton {     private static Singleton instance; ...

  6. java设计模式--解决单例设计模式中懒汉式线程安全问题

    首先写个单例,懒汉模式: public class SingleDemo { private static SingleDemo s = null; private SingleDemo(){} pu ...

  7. java并发笔记之证明 synchronized锁 是否真实存在

    警告⚠️:本文耗时很长,先做好心理准备 证明:偏向锁.轻量级锁.重量级锁真实存在 由[java并发笔记之java线程模型]链接: https://www.cnblogs.com/yuhangwang/ ...

  8. java并发笔记之四synchronized 锁的膨胀过程(锁的升级过程)深入剖析

    警告⚠️:本文耗时很长,先做好心理准备,建议PC端浏览器浏览效果更佳. 本篇我们讲通过大量实例代码及hotspot源码分析偏向锁(批量重偏向.批量撤销).轻量级锁.重量级锁及锁的膨胀过程(也就是锁的升 ...

  9. Java设计模式之单例设计模式总结

    package singleton; /**单例设计模式 饿汉式 * * @author gx *这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化, ...

随机推荐

  1. java并发编程(十三)----(JUC原子类)引用类型介绍(CAS和ABA的介绍)

    这一节我们将探讨引用类型原子类:AtomicReference, AtomicStampedRerence, AtomicMarkableReference.AtomicReference的使用非常简 ...

  2. 扩展欧几里德算法(递归及非递归实现c++版)

    今天终于弄懂了扩展欧几里德算法,有了自己的理解,觉得很神奇,就想着写一篇博客. 在介绍扩展欧几里德算法之前,我们先来回顾一下欧几里德算法. 欧几里德算法(辗转相除法): 辗转相除法求最大公约数,高中就 ...

  3. 大陆争霸[SDOI2010]带限制最短路

    只要你有无限个自爆机器人,你就能为所欲为 斯普林·布拉泽 [题目描述] 略 一句话题意: 杰森国有 \(N\) 个城市,由 \(M\) 条单向道 路连接.杰森国的首都是城市 \(N\).你只需摧毁杰森 ...

  4. 洛谷 P3628 [APIO2010]特别行动队

    题意简述 将n个士兵分为若干组,每组连续,编号为i的士兵战斗力为xi 若i~j士兵为一组,该组初始战斗力为\( s = \sum\limits_{k = i}^{j}xk \),实际战斗力\(a * ...

  5. Python模拟登录淘宝

    最近想爬取淘宝的一些商品,但是发现如果要使用搜索等一些功能时基本都需要登录,所以就想出一篇模拟登录淘宝的文章!看了下网上有很多关于模拟登录淘宝,但是基本都是使用scrapy.pyppeteer.sel ...

  6. php cmd命令行 导入 与备份

  7. web项目jsp中无法引入js问题

    https://blog.csdn.net/C1042135353/article/details/80274685#commentBox 这篇文章超赞的,几个小时的时间看了这篇文章豁然开朗,瞬间懂了 ...

  8. import 和from…import

    import 和from-import 一.import模块名 import time print(time.time()) import首次导入模块发生了3件事: 使用import time导入的时 ...

  9. Facebook的早期历史

    Facemash:谁更有吸引力?Facebook的起源   2003年,当时扎克伯格还是一名哈佛大学的二年级学生,他编写了一个名为Facemash的网站.他利用黑客技术入侵了学校管理部门的网站,并从中 ...

  10. eclipse导入的web项目不能部署到tomcat,显示为java项目

    今天在eclipse中导入之前做个项目,想运行起来看看,发现导入之后没法部署. 先解决办法如下: 右键项目 勾选上面三项并选择相应的值后就变成web项目,可以部署在tomcat上了.