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()这条语句实际上不是一个原子操作,它大概包括三件事:- 给LazySingleton的实例分配内存;
- 初始化LazySingleton()的构造器;
- 将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 ofvolatile
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没有被安全地发布。
满地都是六便士,她却抬头看见了月亮。