Java单例的常见形式

本文目的:总结Java中的单例模式

本文定位:学习笔记

学习过程记录,加深理解,便于回顾。也希望能给学习的同学一些灵感

一、非延迟加载单例类

public class Singleton {

private Singleton() {}

private static final Singleton instance = new Singleton();

public static Singleton getInstance() {

return instance;

}

}

二、同步延迟加载

public class Singleton {

private static Singleton instance = null;

private Singleton() {}

public static synchronized Singleton getInstance() {

if (instance == null) {

instance = new Singleton();

}

return instance;

}

}

三、双重检测同步延迟加载

为了减少同步的开销,于是有了双重检查模式

为处理原版非延迟加载方式瓶颈问题,我们需要对 instance 进行第二次检查,目的是避开过多的同步(因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同获取锁了),但在Java中行不通,因为同步块外面的if (instance == null)可能看到已存在,但不完整的实例。

public class Singleton {

private volatile static Singleton instance = null;

private Singleton() {}

public static Singleton getInstance() {

if (instance == null) {

synchronized(Singleton.class) { // 1

if (instance == null) { // 2

instance = new Singleton(); // 3

}

}

}

return instance;

}

}

双重检测锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是失败的一个主要原因。

无序写入: 为解释该问题,需要重新考察上述清单中的 //3 行。此行代码创建了一个 Singleton 对象并初始化变量 instance 来引用此对象。这行代码的问题是:在 Singleton 构造函数体执行之前,变量 instance 可能成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程得到的是一个还会初始化的对象,这样会导致系统崩溃。 什么?这一说法可能让您始料未及,但事实确实如此。在解释这个现象如何发生前,请先暂时接受这一事实,我们先来考察一下双重检查锁定是如何被破坏的。假设代码执行以下事件序列:

线程 1 进入 getInstance() 方法。

由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。

线程 1 前进到 //3 处,但在构造函数执行之前,使实例成为非 null。

线程 1 被线程 2 预占。

线程 2 检查实例是否为 null。因为实例不为 null,线程 2 将 instance 引用返回给一个构造完整但部分初始化了的 Singleton 对象。

线程 2 被线程 1 预占。

线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。

为展示此事件的发生情况,假设代码行 instance =new Singleton(); 执行了下列伪代码:

mem = allocate(); //为单例对象分配内存空间.

instance = mem; //注意,instance 引用现在是非空,但还未初始化

ctorSingleton(instance); //为单例对象通过instance调用构造函数

这段伪代码不仅是可能的,而且是一些 JIT 编译器上真实发生的。执行的顺序是颠倒的,但鉴于当前的内存模型,这也是允许发生的。JIT 编译器的这一行为使双重检查锁定的问题只不过是一次学术实践而已。

在 Java 中双重检查模式无效的原因是在不同步的情况下引用类型不是线程安全的。对于除了 long 和 double 的基本类型,双重检查模式是适用 的。比如下面这段代码就是正确的:

private int count;

public int getCount(){

if (count == 0){

synchronized(this){

if (count == 0){

count = computeCount(); //一个耗时的计算

}

}

}

return count;

}

上面就是关于java中双重检查模式(double-check idiom)的一般结论。

double-check无效原因可参见

但是事情还没有结束,因为java的内存模式也在改进中。Doug Lea 在他的文章中写道:“根据最新的 JSR133 的 Java 内存模型,如果将引用类型声明为 volatile,双重检查模式就可以工作了”, 参见 。

Section 2.2.7.x on the Memory Model doesn't provide current details of the JSR133 spec revision. (The basic ideas still apply though.) One change that commonly arises in practice is that Section 2.2.7.4 and 2.4.1.2 should say that reading a volatile reference makes visible other changes to the referred object by the thread writing the reference. In particular, double-check idioms work in the expected way when references are declared volatile.

所以以后要在 Java 中使用双重检查模式,可以使用下面的代码:

private volatile Resource resource;

public Resource getResource(){

if (resource == null){

synchronized(this){

if (resource==null){

resource = new Resource();

}

}

}

return resource;

}

四、使用内部类实现延迟加载(推荐)

public class Singleton {

static class SingletonHolder {

static Singleton instance = new Singleton();

}

public static Singleton getInstance(){

return SingletonHolder.instance;

}

}

其他

以上为常见单例形式,另有 ThreadLocal、枚举 等实现形式

此处不作介绍

参考

对本文有什么建议(内容、写作风格等),欢迎留言提出,感谢!

java中单例实现常用的方式_Java单例的常见形式相关推荐

  1. java创建线程池几种方式_java知识总结-创建线程池的6种方式

    一.创建线程池的6种方式: Executors.newCachedThreadPool(); 创建一个可缓存线程池,应用中存在的线程数可以无限大 Executors.newFixedThreadPoo ...

  2. Holder 方式的单例

    一 点睛 Hold 方式的单例完全是借助了类加载的特点. 二 代码 package singleton.singleton3;// final 不允许继承 public final class Sin ...

  3. Java单例模式详解--七种单例模式实现+单例安全+实际应用场景

    单例模式 保证了一个类只有一个实例,并且提供了一个全局访问点.单例模式的主要作用是节省公共资源,方便控制,避免多个实例造成的问题. 实现单例模式的三点: 私有构造函数 私有静态变量维护对象实例 公有静 ...

  4. Java笔记:包装类、toString()方法、单例类、比较(==和equals方法)

    1.包装类 1)包装类为基本数据类型提供了相应的引用数据类型. (基本数据类型-包装类) btye-Byte,char-Character,short-Short,float-Float int-In ...

  5. 抽象单例:一种通用的单例

    背景 单例的一般写法,大家基本都会,这里特指具有以下特征的单例: 1.单例 2.需要延迟加载 3.线程安全 看一下这个类的写法(JAVA): public class A {private stati ...

  6. 公共类java连接数据库_JDBC 建立连接公共操作类(静态方式与单例方式)

    package com.ighost.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql. ...

  7. java 单例类_Java单例类

    单例类: 主要知识点: 1,单例类概念.特点 2,三种单例类懒汉,饿汉,双重加锁举例, 3,懒汉.饿汉区别以及单例类的总结: 1,概念:java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单 ...

  8. java创建具体时间点_java单例饿汉模式对象创建时间点疑问

    关于java单例中饿汉式模式的解释,大多如下: 饿汉模式线程安全的,在类创建的同时就已经创建好一个静态的对象,相对与懒汉模式对象创建过早,浪费空间. 但是jvm中明确定义是:虚拟机规范则是严格规定了有 ...

  9. mongodb java 单例_JAVA单例MongoDB工具类详解

    shasha 2018年09月07日 681 0 JAVA单例MongoDB工具类 JAVA驱动版本: org.mongodb mongo-java-driver 3.0.2 工具类代码如下: pac ...

最新文章

  1. python源码分析工具_python 域名分析工具实现代码
  2. 贪心法——最优装载问题
  3. CALayer 简单的使用
  4. [转载]Qt之模型/视图(自定义风格)
  5. linux 下配置jdk
  6. 4. 正则表达式(4)
  7. [SCOI2012]喵星球上的点名(树状数组+后缀数组)
  8. Oracle等待事件之Enqueue(锁)
  9. Java 邮政EMS快递面打印实现 笔记
  10. 电子信息工程考研专业方向解读
  11. 【吐血整理】185道大数据面试题及答案
  12. 《嵌入式开发》实验项目
  13. 分布式追踪不是银弹 | 正确使用分布式追踪和 APM 系统
  14. css3 transition属性实现三角形
  15. PostgreSQL数据库连接
  16. android 64位系统中,需要引用32位库,导致挂掉的问题
  17. win10wifi间歇性断网重启后恢复_win10间接性断网怎么解决_win10电脑网络老是间歇性断网如何恢复-win7之家...
  18. 一个简单的Java程序:My first Java!
  19. 如何利用MATLAB进行数据插值?
  20. 分支结构 单分支多分支嵌套分支结构

热门文章

  1. 如何在LINUX里用su切换用户
  2. 什么检索是借助计算机技术进行自动标引的,自动文献检索系统
  3. python可以做测试软件吗_Python如何给你的程序做性能测试
  4. javascript 字符串中间隔固定位置插入字符
  5. loguru log 日志的使用
  6. Jupyter Notebook 的快捷键
  7. 三值网络--Trained Ternary Quantization
  8. 解决报错: MobaXterm X11 proxy: Unsupported authorisation protocol
  9. GO语言教程2:使用VS code进行go语言的编写和运行
  10. 打造自己的树莓派监控系统2--内存监控-matplotlib显示数据