博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java单例模式双重检查
阅读量:7176 次
发布时间:2019-06-29

本文共 3297 字,大约阅读时间需要 10 分钟。

hot3.png

// Single threaded versionclass Foo {    private Helper helper = null;    public Helper getHelper() {        if (helper == null) {            helper = new Helper();        }        return helper;    }     // other functions and members...}

这段在使用多线程的情况下无法正常工作。在多个线程同时调用getHelper()时,必须要获取,否则,这些线程可能同时去创建对象,或者某个线程会得到一个未完全初始化的对象。

锁可以通过代价很高的同步来获得,就像下面的例子一样。

// Correct but possibly expensive multithreaded versionclass Foo {    private Helper helper = null;    public synchronized Helper getHelper() {        if (helper == null) {            helper = new Helper();        }        return helper;    }     // other functions and members...}

只有getHelper()的第一次调用需要同步创建对象,创建之后getHelper()只是简单的返回成员变量,而这里是无需同步的。 由于同步一个方法会降低100倍或更高的性能, 每次调用获取和释放锁的开销似乎是可以避免的:一旦初始化完成,获取和释放锁就显得很不必要。许多程序员一下面这种方式进行优化:

  1. 检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量。
  2. 获取锁
  3. 第二次检查变量是否已经被初始化:如果其他线程曾获取过锁,那么变量已被初始化,返回初始化的变量。
  4. 否则,初始化并返回变量。
// Broken multithreaded version// "Double-Checked Locking" idiomclass Foo {    private Helper helper = null;    public Helper getHelper() {        if (helper == null) {            synchronized(this) {                if (helper == null) {                    helper = new Helper();                }            }        }        return helper;    }     // other functions and members...}

直觉上,这个算法看起来像是该问题的有效解决方案。然而,这一技术还有许多需要避免的细微问题。例如,考虑下面的事件序列:

  1. 线程A发现变量没有被初始化, 然后它获取锁并开始变量的初始化。
  2. 由于某些编程语言的语义,编译器生成的代码允许在线程A执行完变量的初始化之前,更新变量并将其指向部分初始化的对象。
  3. 线程B发现共享变量已经被初始化,并返回变量。由于线程B确信变量已被初始化,它没有获取锁。如果在A完成初始化之前共享变量对B可见(这是由于A没有完成初始化或者因为一些初始化的值还没有穿过B使用的内存()),程序很可能会崩溃。

在或更早的版本中使用双重检查锁有潜在的危险,有时会正常工作:区分正确和有小问题的实现是很困难的。取决于,线程的和其他,不正确的实现双重检查锁导致的异常结果可能会间歇性出现。重现异常是十分困难的。

在中,这一问题被修正了。关键字保证多个线程可以正确处理单件实例。描述了这一新的语言特性:

// Works with acquire/release semantics for volatile// Broken under Java 1.4 and earlier semantics for volatileclass Foo {    private volatile Helper helper = null;    public Helper getHelper() {        Helper result = helper;        if (result == null) {            synchronized(this) {                result = helper;                if (result == null) {                    helper = result = new Helper();                }            }        }        return result;    }     // other functions and members...}

注意局部变量result的使用看起来是不必要的。对于某些版本的,这会使代码提速25%,而对其他的版本则无关痛痒。

如果helper对象是静态的(每个类只有一个), 可以使用双重检查锁的替代模式模式。查看 上的列表16.6。

// Correct lazy initialization in Java@ThreadSafeclass Foo {    private static class HelperHolder {       public static Helper helper = new Helper();    }     public static Helper getHelper() {        return HelperHolder.helper;    }}

这是因为内部类直到他们被引用时才会加载。

Java 5中的final语义可以不使用volatile关键字实现安全的创建对象:

public class FinalWrapper
{ public final T value; public FinalWrapper(T value) { this.value = value; }} public class Foo { private FinalWrapper
helperWrapper = null; public Helper getHelper() { FinalWrapper
wrapper = helperWrapper; if (wrapper == null) { synchronized(this) { if (helperWrapper == null) { helperWrapper = new FinalWrapper
(new Helper()); } wrapper = helperWrapper; } } return wrapper.value; }}

为了正确性,局部变量wrapper
是必须的。这一实现的性能不一定比使用volatile
的性能更高。

转载于:https://my.oschina.net/u/138995/blog/183920

你可能感兴趣的文章
iOS开发基础知识--碎片26
查看>>
制作鼠标移动到div上面显示弹出框
查看>>
java既然存在gc线程,为什么还存在内存泄漏?
查看>>
Linux Vim
查看>>
UVA - 11987 Almost Union-Find[并查集 删除]
查看>>
使用css开启硬件加速提高网站性能
查看>>
CMakeLists.txt的写法
查看>>
Flask如何使用https?
查看>>
Linux命令之kill
查看>>
Asp.Net SignalR Hub类中的操作详解
查看>>
附上解决迅雷9及迅雷极速版任何资源下载任务出错、内容违规问题
查看>>
hadoop3: mkdir: cannot create directory `/usr/local/hadoop/bin/../logs’: Permission denied
查看>>
高阶函数与面向对象继承的比较
查看>>
群雄逐鹿的移动互联网时代【转载】
查看>>
【排序】InsertSort
查看>>
[c++11]多线程编程(五)——unique_lock
查看>>
漫谈promise使用场景
查看>>
Design Pattern的万剑归宗 => Mediator
查看>>
Javascript中的原型继承的一些看法与见解
查看>>
HackerRank:JavaScript 是最知名的编程语言
查看>>