Singleton

  • 只能有一个实例;必须自己创建自己的实例;必须给其他所有对象提供这一实例

实现方法

饿汉式singleton

  • 预先加载法
  • class Single {
    private Single() {
    System.out.println("ok");
    } private static Single instance = new Single(); public static Single getInstance() {
    return instance;
    }
    }
  • 优点:
    • thread safe
    • 调用时速度快(在类加载时已经创建好一个static对象)
  • 缺点:
    • 资源利用率不高(可能系统不需要)
    • 在一些场景下无法使用。比如在single实例的创建依赖参数或配置文件时。

懒汉式singleton

  • 延迟加载法
  • public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {} public static LazySingleton getInstance() {
    if (instance == null) {
    instance = new LazySingleton();
    }
    return instance;
    }
    }
  • 适用于单线程环境,not trhead-safe,getInstance()方法可能返回两个不同实例。
  • 可以改成thread-safe版本,如下:
    public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {} public static synchronized LazySingleton getInstance() {
    if (instance == null) {
    instance = new LazySingleton();
    }
    return instance;
    }
    }
  • 优点:不执行getInstance对不会被实例化
  • 缺点:第一次加载时反应不快。每次调用getInstance的同步开销大。(大量不必要的同步)

DCL singleton

  • Double Check Lock
  • 避免每次调用getInstance方法时都同步
  • public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {} public static LazySingleton getInstance() {
    if (instance == null) {
    synchronized(LazySingleton.class) {
    if (instance == null) {
    instance = new LazySingleton();
    }
    }
    }
    return instance;
    }
    }
  • 第一层判断,避免不必要的同步。第二层判断则是在线程安全的情况下创建实例。
  • 优点:资源利用率高,多线程下效率高。
  • 缺点:第一次加载时反应不快,由于java内存模型一些原因偶尔会失败,在高并发下有一定的缺陷。
  • 上述代码依然存在不安全性:
    instance = new LazySingleton()这条语句实际上不是一个原子操作,它大概包括三件事:
    1. 给LazySingleton的实例分配内存;
    2. 初始化LazySingleton()的构造器;
    3. 将instance对象指向分配的内存空间(在这一步的时候instance变成非null)。

  但是由于Java编译器允许处理器乱序执行(指令重排序),上述2、3点的顺序是无法保证的。(意思是可能instance != null时有可能还未真正初始化构造器)。 
  解决方法是通过将instance定义为volatile的。(volatile有两个语义:1. 保证变量的可见性;2. 禁止对该变量的指令重排序)

  • 参考<<Java并发编程>> P286 ~ P287。在JMM后续版本(>= Java5.0)中,可以通过结合volatile的方式来启动DCL,并且该方式对性能的影响很小。然而,DCL的这种使用方式已经被广泛地抛弃了。
  • (因为volatile屏蔽指令重排序的语义在JDK1.5中才被完全修复,此前的JDK中即使将变量声明为volatile,也仍然不能完全避免重排序所导致的问题,这主要是因为volatile变量前后的代码仍然存在重排序问题。)
  • (Java5中多了一条happens-before的规则:对volatile字段的写操作happens-before后续对同一个字段的读操作)

static内部类singleton

  • class Single {
    private Single() {} private static class InstanceHolder {
    private static final Single instance = new Single();
    } public static Single getInstance() {
    return InstanceHolder.instance();
    }
    }
  • 优点:线程安全,资源利用率高。
  • 缺点:第一次加载时反应不快。
  • 原理:类级内部类(static修饰的成员内部类)只有在第一次使用时才会被加载。

Summary

  • 考虑到效率、安全性等问题,一般常用饿汉式singleton or static内部类singleton。其中后者是常用的singleton实现方法。

Happens-before

  • 是否可以通过几个基本的happens-before规则从理论上分析Java多线程程序的正确性,而不需要设计到硬件和编译器的知识呢?

Happens-before规则

  • 通俗来说,A happens-before B意味着操作A对内存施加的影响都能被B观测到。
  • 关于happens-before:
    • happens-before relation on memory operations such reads and writes of shared varaiables.
    • In particular:
      • Each action in a thread happens-before every action in that thread that comes later in the program's order. (单线程规则)
      • An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor. (线程安全性主要依赖这条规则)
      • A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.
      • A call to start on a thread happens-before any action in the started thread.
      • All actions in a thread happen-before any other thread successfully returns from a join on that thread.
      • happens-before关系具有传递性。 hb(A, B) + hb(B, C) => hb(A, C)
  • 要知道,“A在时间上先于B”和“A happens-before B”两者并不等价。
  • 两个操作之间必然存在某种时序关系(先后or同时),但两个操作之间却不一定存在happens-before关系。但两个存在happens-before关系的操作不可能同时发生,这实际上也是同步的语义之一(独占访问)。
  • 以及,上述一直提到的操作并不等同于语句。操作应该是单个虚拟机指令,单条语句可能由多个指令组成。

Happens-before & DCL

  • DCL(without volatile)的主要问题在于尽管得到了LazySingleton的引用,但却有可能访问到其成员变量的不正确值。
  • 重新分析上述DCL例子:
    public class LazySingleton {
    private int someField; private static LazySingleton instance; private LazySingleton() {
    this.someField = new Random().nextInt(200)+1; // (1)
    } public static LazySingleton getInstance() {
    if (instance == null) { // (2)
    synchronized(LazySingleton.class) { // (3)
    if (instance == null) { // (4)
    instance = new LazySingleton(); // (5)
    }
    }
    }
    return instance; // (6)
    } public int getSomeField() {
    return this.someField; // (7)
    }
    }
  • DCL产生安全问题的主要原因就在于:(1) & (7) 之间不存在happens-before关系。
  • 这个例子中LazySingleton是一个不变类,它只有get而没有set方法。但上述例子让我们知道,即使一个对象是不变的,在不同的线程中也可能返回不同值。这是因为LazySingleton没有被安全地发布。

[Java] [Singleton] [DCL][happens-before]的更多相关文章

  1. Java Singleton 单例模式

    大家可能还听过 Singleton  也就是单例模式 这个单例模式要求 在程序的运行时候   一个程序的某个类 只允许产生一个 实例 那么 这个类就是一个单例类 Java Singleton模式主要作 ...

  2. Java Singleton的3种实现方式

    1.通过静态成员字段来实例化 public class Elvis { /** * 通过final的静态成员字段来调用私有的构造函数实例化对象 */ public static final Elvis ...

  3. Java Singleton(单例模式) 实现详解

    什么是单例模式? Intend:Ensure a class only has one instance, and provide a global point of access to it. 目标 ...

  4. Java并发——DCL问题

    转自:http://www.iteye.com/topic/875420 如果你搜索网上分析dcl为什么在java中失效的原因,都会谈到编译器会做优化云云,我相信大家看到这个一定会觉得很沮丧.很无助, ...

  5. Java singleton 一例

    org.springframework.beans.factory.support.DefaultListableBeanFactory - Returning cached instance of ...

  6. Java Singleton Implementation

    概述 Java中单例模式的实现有多重方法, 要实现单例模式主要的问题是线程安全问题以及对Lazy Load的考虑,主要有如下几种 双重锁定懒加载单例 预加载单例 枚举单例 双重锁定懒加载单例模式 /* ...

  7. java singleton(单例设计模式)

    单例设计模式的主要作用是: 1.控制资源的使用,我们对资源使用线程同步来实现并发访问. 2.节约资源,我们对一个类只进行一个实例化进行全局的资源访问,节约了内存. 3.作为通信媒介,也是数据共享,可以 ...

  8. Java singleton 单例

    饿汉式,instance在类加载化时完成初始化,线程安全 package cookie; public class SingletonAtOnce { private SingletonAtOnce( ...

  9. The Java Enum: A Singleton Pattern [reproduced]

    The singleton pattern restricts the instantiation of a class to one object. In Java, to enforce this ...

随机推荐

  1. Python3爬虫相关软件,库的安装

    Anaconda 百度搜Anaconda清华,根据环境选择版本下载 安装时记得勾选添加到环境变量,不要还要手动添加 Anaconda Navigator可视化界面,可以方便地调用Jupyter等工具. ...

  2. vim实现实时自动保存

    进https://www.vim.org/scripts/script.php?script_id=4521网站下载vim -auto-save wget  https://www.vim.org/s ...

  3. 简单谈谈$.merge()

    var arr1 = [1,2,3]; var arr2 = [1,2,3]; console.log($.merge(arr1,arr2)) //[1,2,3,1,2,3],可见数组间只是合并,不会 ...

  4. HDU 5299 Circles Game

    HDU 5299 思路: 圆扫描线+树上删边博弈 圆扫描线有以下四种情况,用set维护扫描线与圆的交点,重载小于号 代码: #pragma GCC optimize(2) #pragma GCC op ...

  5. springboot 启动报错

    有一个警告 :** WARNING ** : Your ApplicationContext is unlikely to start due to a @ComponentScan of the d ...

  6. discuss!X3.4 帖子显示昵称而不是用户名的解决办法

    问题:dedecmsV5.7和discuz!X3.4整合之后,实现免激活登陆之后,从dede过来的用户在discuz 直接展示的用户名,因为我们的用户名是手机号,所以不想帖子都展示的是用户名. 因为我 ...

  7. Jupyter Notebook 修改默认打开的文件夹的位置

    初次使用Jupyter Notebook,确实好用啊!!,又好看又好用,不过还是遇到了一个问题,安装好之后,打开Jupyter Notebook 的时候,默认的文件夹的位置是C盘下面的XXX目录,但是 ...

  8. C# 异步(上)

    新进阶的程序员可能对async.await用得比较多,却对之前的异步了解甚少.本人就是此类,因此打算回顾学习下异步的进化史. 本文主要是回顾async异步模式之前的异步,下篇文章再来重点分析async ...

  9. Linux—shell中$(( ))、$( )、``与${ }的区别

    命令替换 在bash中,$( )与` `(反引号)都是用来作命令替换的.命令替换与变量替换差不多,都是用来重组命令行的,先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行. exp 1 [ ...

  10. Chromedriver executable needs to be in path 解决办法

    执行webdriver.Chrome()时报错:Chromedriver executable needs to be in path. 原因可能是为有安装Chromedriver 可能是Chrome ...