在现代JVM中双重检查锁定

时间:2012-02-15 09:08:13

标签: java multithreading synchronization

我有一个类可能会在初始化期间抛出任何运行时异常。我希望这个类是一个单例,因为在内存中保存多个对象的成本很高。我在另一堂课中使用那门课。

我的用例如下:

  • 我必须使用Controller
  • 的单个实例
  • Parent的每个实例都必须使用相同的Controller实例。
  • Controller 构造函数可能会抛出异常。
  • 如果实例化失败,我应该 一段时间后重试实例化。

所以当我尝试在null上执行“get”时,我检查我的Controller实例是否为Controller,如果是,我会尝试再次实例化它。

以下是我的代码:

class Parent
{
    private static volatile Controller controller;
    private static final Object lock = new Object();

    static
    {
        try
        {
            controller = new Controller();
        }
        catch(Exception ex)
        {
            controller = null;
        }
    }

    private Controller getController() throws ControllerInstantiationException
    {
        if(controller == null)
        {
            synchronized(lock)
            {
                if(controller == null)
                {
                    try
                    {
                        controller = new Controller();
                    }
                    catch(Exception ex)
                    {
                        controller = null;
                        throw new ControllerInstatntationException(ex);
                    }
                }
            }
        }
        return controller;
    }

    //other methods that uses getController() 
}

我的问题是,这段代码坏了吗?我在某处读到上述代码在JVM 1.4或更早版本中会出现问题。你能提供参考/解决方案吗?请注意,我问的是这个问题,因为互联网上有很多关于这个主题的混淆。

感谢。

5 个答案:

答案 0 :(得分:5)

我认为它没有被破坏,导致volatile声明。但是imho最好避免像这样的代码。无法保证此代码适用于Java 8。还有另一种方法来创建懒惰的单身人士。我总是(差不多)使用这种方法。第一次在Java Concurrency in Practice中面对它。

public class Singleton {
        private Singleton() { }

        private static class SingletonHolder { 
                public static final Singleton instance = new Singleton();
        }

        public static Singleton getInstance() {
                return SingletonHolder.instance;
        }
}

我不知道你在代码中做了什么,很难说,如何调整它。最简单的方法,只需使用synchronize方法。您是否真的希望使用双重检查锁定获得一些性能优势?是否有瓶颈同步方法?

答案 1 :(得分:1)

唯一被打破的是让这个例子远比它需要的复杂得多。

你需要的只是一个枚举

// a simple lazy loaded, thread safe singleton.
enum Controller {
    INSTANCE
}

答案 2 :(得分:1)

使用AtomicBoolean(很像我建议的here)会更安全,并允许在失败时重复尝试实例化。

public static class ControllerFactory {
  // AtomicBolean defaults to the value false.
  private static final AtomicBoolean creatingController = new AtomicBoolean();
  private static volatile Controller controller = null;

  // NB: This can return null if the Controller fails to instantiate or is in the process of instantiation by another thread.
  public static Controller getController() throws ControllerInstantiationException {
    if (controller == null) {
      // Stop another thread creating it while I do.
      if (creatingController.compareAndSet(false, true)) {
        try {
          // Can fail.
          controller = new Controller();
        } catch (Exception ex) {
          // Failed init. Leave it at null so we try again next time.
          controller = null;
          throw new ControllerInstantiationException(ex);
        } finally {
          // Not initialising any more.
          creatingController.set(false);
        }
      } else {
        // Already in progress.
        throw new ControllerInstantiationException("Controller creation in progress by another thread.");
      }
    }
    return controller;
  }

  public static class ControllerInstantiationException extends Exception {
    final Exception cause;

    public ControllerInstantiationException(Exception cause) {
      this.cause = cause;
    }

    public ControllerInstantiationException(String cause) {
      this.cause = new Exception(cause);
    }
  }

  public static class Controller {
    private Controller() {
    }
  }
}

答案 3 :(得分:1)

是的,它可以保证在现代JVM上使用Java Memory Model。请参阅The "Double-Checked Locking is Broken" Declaration在新的Java内存模型下部分。

正如其他答案所指出的那样,使用Holder类或枚举有更简单的单例模式。但是,在像您这样的情况下,如果第一次尝试失败,您希望允许尝试重新初始化多次,我相信使用volatile实例变量进行双重检查是正常的。

答案 4 :(得分:0)

这不是你的问题的答案,但这篇关于Double-Checked Locking is Broken的着名文章解释了为什么它在java 1.4或更早版本中被破坏了。