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. CentOS7.X怎样更新yum源

    1. 备份原来的yum源 $sudo cp /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo-backup 2.设 ...

  2. C# interface 的隐式与显示实现及适应范围源码演示

    把代码过程中经常用到的一些代码段做个记录,如下的资料是关于C# interface 的隐式与显示实现及适应范围演示的代码. interface IAnimal { void Dog(); } clas ...

  3. flutter -------- ListView的使用

    学习了Flutter,来分享一下学习的一些常用的知识,先来说说ListView 案例效果: ListView是一个类似列的widget,它的内容对于其渲染框太长时会自动提供滚动. ListView 摘 ...

  4. 多个vlan之间路由

    通过这节课,我们学习到了如何利用两个交换机,创建多个vlan口,将多台Pc之间互通今天学习到了新的指令:interface vlan-interfacee ?                     ...

  5. XVI Open Cup named after E.V. Pankratiev. GP of Ekaterinburg--I.Iron man

    n个服务器,k类任务,每个服务器完成一个任务都有特定的花费$cost_{i,j}$,但是你设置好某台机器去完成某项任务时他只能去完成这类任务,除非你可以花费$C$去更改配置.第$i$天要求去完成$q_ ...

  6. Android测试(三)——burpsuite抓包设置

    导出证书: 将证书导入模拟器中: 设置监听端口,透明代理(一定要设置这个): 进入adb shell,输入如下命令,即可抓包了:  iptables -t nat -A OUTPUT -p tcp - ...

  7. hdu6415 记忆化搜索或找规律

    Rikka with Nash Equilibrium Time Limit: / MS (Java/Others) Memory Limit: / K (Java/Others) Total Sub ...

  8. nginx 出现504 Gateway Time-out的解决方法

    本文介绍nginx出现504 Gateway Time-out问题的原因,分析问题并提供解决方法. 1.问题分析nginx访问出现504 Gateway Time-out,一般是由于程序执行时间过长导 ...

  9. Safari 里的javascript 里不能用submit作为函数名

    Safari 里的javascript 里不能用submit作为函数名, 这样写的时候,怎么也运行不了JeasyUI的onSubmit的function, 改个名就可以了.而在chrome下面就没问题 ...

  10. Public_1.bat

    :: targetset targetFileName=Public_2set targetPath=./:: sourceset sourceFileName=Public_1@echo ui ru ...