Java多线程 -- 单例模式的Double-checked Locking (DCL)问题

Double-checked Locking (DCL)用来在lazy initialisation 的单例模式中避免同步开销的一个方法。

下面是这么做的一个例子。

[java] view
plain
copy

  1. public class MyFactory {
  2. private static MyFactory instance;
  3. public synchronized static MyFactory getFactory() {
  4. if (instance == null)
  5. instance = new MyFactory();
  6. return instance;
  7. }
  8. }

上面的例子是完全正确的。但是考虑到所有的Read操作也需要同步,为了避免昂贵的同步开销,似乎有如下做法:

[java] view
plain
copy

  1. public class MyBrokenFactory {
  2. private static MyFactory instance;
  3. private int field1, field2 ...
  4. public static MyBrokenFactory getFactory() {
  5. // This is incorrect: don‘t do it at home, kids!
  6. if (instance == null) {
  7. synchronized (MyBrokenFactory.class) {
  8. if (instance == null)
  9. instance = new MyFactory();
  10. }
  11. }
  12. return instance;
  13. }
  14. private MyBrokenFactory() {
  15. field1 = ...
  16. field2 = ...
  17. }
  18. }

但是上面的做法是不正确的,考虑2个线程同时调用MyBrokenFactory.getFactory(),线程2在线程1完成对象的初始化之前就可能得到了对象的引用。

Thread 1: ‘gets in first‘ and starts creating instance.

Thread 2: gets in just as Thread 1 has written the object reference to memory, but before it has written all thefields.

1. Is instance null? Yes.

2. Synchronize on class.

3. Memory is allocated for instance.

4. Pointer to memory saved into instance.

7. Values for field1 and field2 are written to memory allocated for object.

5. Is instance null? No.

6. instance is non-null, but field1 and field2 haven‘t yet been set! This thread sees invalid values for field1 and field2!

如果解决上面的问题呢?

方法1:使用class loader

[java] view
plain
copy

  1. public class MyFactory {
  2. private static final MyFactory instance = new MyFactory();
  3. public static MyFactory getInstance() {
  4. return instance;
  5. }
  6. private MyFactory() {}
  7. }

如果需要处理异常情况,

[java] view
plain
copy

  1. public class MyFactory {
  2. private static final MyFactory instance;
  3. static {
  4. try {
  5. instance = new MyFactory();
  6. } catch (IOException e) {
  7. throw new RuntimeException("Darn, an error‘s occurred!", e);
  8. }
  9. }
  10. public static MyFactory getInstance() {
  11. return instance;
  12. }
  13. private MyFactory() throws IOException {
  14. // read configuration files...
  15. }
  16. }

但是这样就失去了lazy initialisation带来的好处,Java5以后还有一种办法,

方法2:使用DCL+volatile

JAVA5以后如果申明实例引用为volatile,那么DCL就是OK的。

[java] view
plain
copy

  1. public class MyFactory {
  2. private static volatile MyFactory instance;
  3. public static MyFactory getInstance(Connection conn)
  4. throws IOException {
  5. if (instance == null) {
  6. synchronized (MyFactory.class) {
  7. if (instance == null)
  8. instance = new MyFactory(conn);
  9. }
  10. }
  11. return instance;
  12. }
  13. private MyFactory(Connection conn) throws IOException {
  14. // init factory using the database connection passed in
  15. }
  16. }

JAVA5以后,访问一个volatile的变量具有synchronized 的语义。换句话说,JAVA5保证unsycnrhonized volatile read 会在写之后发生。(Accessing a volatile variable has the semantics of synchronization as of Java 5. In other words Java
5 ensures that the unsycnrhonized volatile readmust happen after the write has taken place。)

关于volatile的详细说明http://blog.csdn.net/fw0124/article/details/6669984

方法3:Factory类的所有字段都是final字段

JAVA5之后,如果在constructor中对final字段赋值,JVM保证先把这些值提交到内存,然后才会更新内存中的对象引用。

换句话说,另外一个能看到这个对象的线程不能看到没有初始化过的final字段。

在Factory类的所有字段都是final字段这种情况下,我们实际上没有必要申明factory实例为volatile。

In Java 5, a change was made to the definition of final fields. Where the values of these fields are set in the constructor, the JVM ensures that these values are committed to main memorybefore
the object reference itself. In other words, another thread that can "see" the objectcannot ever see uninitialised values of its final fields. In that case, we wouldn‘t actually need to declare the instance reference as volatile.

(原文http://javamex.com/tutorials/double_checked_locking_fixing.shtml)

方法4:使用静态内部类

在类的初始化期间,JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。基于这个特性,可以实现另一种线程安全的延迟初始化方案。

[java] view
plain
copy

  1. public class SingleFactory {
  2. private static class InstanceHolder {
  3. public static final SingleFactory instance = new SingleFactory();
  4. }
  5. public static SingleFactory getInstance() {
  6. return InstanceHolder.instance;
  7. }
  8. private SingleFactory() {
  9. }
  10. }

初始化一个类,包括执行这个类的静态初始化(static代码段)和初始化在这个类中声明的静态字段。

根据java语言规范,在首次发生下列任意一种情况时,一个类或接口类型T将被立即初始化:

- T是一个类,而且一个T类型的实例被创建;

- T是一个类,且T中声明的一个静态方法被调用;

- T中声明的一个静态字段被赋值;

- T中声明的一个静态字段被使用,而且这个字段不是一个常量字段;

- T是一个顶级类(top level class,见java语言规范的§7.6),而且一个断言语句嵌套在T内部被执行。

以前转载了Java内存模型的系列文章,理解了这些文章,上面的内容就很好理解了。

Java多线程 -- 深入理解JMM(Java内存模型) --(一)基础

Java多线程 -- 深入理解JMM(Java内存模型) --(二)重排序

Java多线程 -- 深入理解JMM(Java内存模型) --(三)顺序一致性

Java多线程 -- 深入理解JMM(Java内存模型) --(四)volatile

Java多线程 -- 正确使用Volatile变量

Java多线程 -- 深入理解JMM(Java内存模型) --(五)锁

Java多线程 -- 深入理解JMM(Java内存模型) --(六)final

Java多线程 -- 深入理解JMM(Java内存模型) --(七)总结

时间: 2024-10-04 20:13:13

Java多线程 -- 单例模式的Double-checked Locking (DCL)问题的相关文章

Double Checked Locking 模式

转自:http://blog.csdn.net/wwsoon/article/details/1485886 之前在使用Double Check Locking 模式时,发现自己还是不太理解.于是写个记录,其实很简单,一看就明白了.应用特别说明:1.Double Check Locking模式是singleton的多线程版本,如果是单线程则应使用singleton.2.Double Check Locking模式依就会使用锁--临界区锁定,不要以为可以避免使用锁.3.Double Check L

java——多线程——单例模式的static方法和非static方法是否是线程安全的?

单例模式的static方法和非static方法是否是线程安全的? 答案是:单例模式的static方法和非static方法是否是线程安全的,与单例模式无关.也就说,如果static方法或者非static方法不是线程安全的,那么不会因为这个类使用了单例模式,而变的安全. 闲话休说,看代码: import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestSingl

java 多线程-单例模式

单例模式对外只有一个对象,对内不管,比如每台电脑都只有一个的任务管理器1.构造去私有化2.内部提供私有静态属性--存储对象的地址3.提供公共的静态方法访--获取属性 public class my { //2.提供私有静态属性 private volatile static my instance; //避免new对象时指令重排 //1.构造器私有化 private my(){ } //3.提供公共静态方法访问属性 public static my getInstance() { if(null

单例模式、双检测锁定DCL、volatile详解

单例模式最要关心的则是对象创建的次数以及何时被创建. Singleton模式可以是很简单的,它的全部只需要一个类就可以完成(看看这章可怜的UML图).但是如果在"对象创建的次数以及何时被创建"这两点上较真起来,Singleton模式可以相当的复杂,比头五种模式加起来还复杂,譬如涉及到DCL双锁检测(double checked locking)的讨论.涉及到多个类加载器(ClassLoader)协同时.涉及到跨JVM(集群.远程EJB等)时.涉及到单例对象被销毁后重建等.对于复杂的情况

单例模式、双检测锁定DCL、volatile(转)

单例模式最要关心的则是对象创建的次数以及何时被创建. Singleton模式可以是很简单的,它的全部只需要一个类就可以完成(看看这章可怜的UML图).但是如果在“对象创建的次数以及何时被创 建”这两点上较真起来,Singleton模式可以相当的复杂,比头五种模式加起来还复杂,譬如涉及到DCL双锁检测(double checked locking)的讨论.涉及到多个类加载器(ClassLoader)协同时.涉及到跨JVM(集群.远程EJB等)时.涉及到单例对象被销毁后重建 等.对于复杂的情况,本章

JAVA中单例模式的几种实现方式

1 线程不安全的实现方法 首先介绍java中最基本的单例模式实现方式,我们可以在一些初级的java书中看到.这种实现方法不是线程安全的,所以在项目实践中如果涉及到线 程安全就不会使用这种方式.但是如果不需要保证线程安全,则这种方式还是不错的,因为所需要的开销比较小.下面是具体的实现代码: 转http://www.cnblogs.com/CodeGuy/p/3580486.html public Class Singleton { private static Singleton instance

50个Java多线程面试题

不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java 语言一个重要的特点就是内置了对并发的支持,让 Java 大受企业和程序员的欢迎.大多数待遇丰厚的 Java 开发职位都要求开发者精通多线程技术并且有丰富的 Java 程序开发.调试.优化经验,所以线程相关的问题在面试中经常会被提到. 在典型的 Java 面试中, 面试官会从线程的基本概念问起, 如:为什么你需要使用线程, 如何创建线程,用什么方式创建线程比较好(比如:继承 thread 类还是调用 Runnable 接口),

java多线程编码注意事项

Sole purpose of using concurrency is to produce scalable and faster program. But always remember, speed comes after correctness. Your Java program must follow its invariant in all conditions, which it would, if executed in sequential manner. If you a

Java多线程核心技术(五)单例模式与多线程

本文只需要考虑一件事:如何使单例模式遇到多线程是安全的.正确的 1.立即加载 / "饿汉模式" 什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 new 实例化. public class MyObject { private static MyObject myObject = new MyObject(); public MyObject(){ } public static MyObject getInstance(){ return myObj