我有一个类可能会在初始化期间抛出任何运行时异常。我希望这个类是一个单例,因为在内存中保存多个对象的成本很高。我在另一堂课中使用那门课。
我的用例如下:
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或更早版本中会出现问题。你能提供参考/解决方案吗?请注意,我问的是这个问题,因为互联网上有很多关于这个主题的混淆。
感谢。
答案 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或更早版本中被破坏了。