`
fighterhou
  • 浏览: 14692 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

双重检查锁定模式-转自维基百科

阅读更多
原文地址:http://zh.wikipedia.org/wiki/%E5%8F%8C%E9%87%8D%E6%A3%80%E6%9F%A5%E9%94%81%E5%AE%9A%E6%A8%A1%E5%BC%8F

双重检查锁定模式(也被称为"双重检查加锁优化","锁暗示"(Lock hint)[1]) 是一种软件设计模式用来减少并发系统中竞争和同步的开销。双重检查锁定模式首先验证锁定条件(第一次检查),只有通过锁定条件验证才真正的进行加锁逻辑并再次验证条件(第二次检查)。
该模式在某些语言在某些硬件平台的实现可能是不安全的。有的时候,这一模式被看做是反模式。
它通常用于减少加锁开销,尤其是为多线程环境中的单例模式实现“惰性初始化”。惰性初始化的意思是直到第一次访问时才初始化它的值。

考虑下面的Java代码
// Single threaded version
class 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 version
class Foo {
    private Helper helper = null;
    public synchronized Helper getHelper() {
        if (helper == null) {
            helper = new Helper();
        }
        return helper;
    }
 
    // other functions and members...
}

只有getHelper()的第一次调用需要同步创建对象,创建之后getHelper()只是简单的返回成员变量,而这里是无需同步的。 由于同步一个方法会降低100倍或更高的性能[2], 每次调用获取和释放锁的开销似乎是可以避免的:一旦初始化完成,获取和释放锁就显得很不必要。许多程序员一下面这种方式进行优化:
检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量。
获取锁
第二次检查变量是否已经被初始化:如果其他线程曾获取过锁,那么变量已被初始化,返回初始化的变量。
否则,初始化并返回变量。
// Broken multithreaded version
// "Double-Checked Locking" idiom
class 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...
}

直觉上,这个算法看起来像是该问题的有效解决方案。然而,这一技术还有许多需要避免的细微问题。例如,考虑下面的事件序列:
线程A发现变量没有被初始化, 然后它获取锁并开始变量的初始化。
由于某些编程语言的语义,编译器生成的代码允许在线程A执行完变量的初始化之前,更新变量并将其指向部分初始化的对象。
线程B发现共享变量已经被初始化,并返回变量。由于线程B确信变量已被初始化,它没有获取锁。如果在A完成初始化之前共享变量对B可见(这是由于A没有完成初始化或者因为一些初始化的值还没有穿过B使用的内存(缓存一致性)),程序很可能会崩溃。

在J2SE 1.4或更早的版本中使用双重检查锁有潜在的危险,有时会正常工作:区分正确实现和有小问题的实现是很困难的。取决于编译器,线程的调度和其他并发系统活动,不正确的实现双重检查锁导致的异常结果可能会间歇性出现。重现异常是十分困难的。
在J2SE 5.0中,这一问题被修正了。volatile关键字保证多个线程可以正确处理单件实例。[4]描述了这一新的语言特性:
// Works with acquire/release semantics for volatile
// Broken under Java 1.4 and earlier semantics for volatile
class 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的使用看起来是不必要的。对于某些版本的Java虚拟机,这会使代码提速25%,而对其他的版本则无关痛痒。[3]
如果helper对象是静态的(每个类只有一个), 可以使用双重检查锁的替代模式惰性初始化模式[4]。查看[5] 上的列表16.6。
// Correct lazy initialization in Java
@ThreadSafe
class Foo {
    private static class HelperHolder {
       public static Helper helper = new Helper();
    }
 
    public static Helper getHelper() {
        return HelperHolder.helper;
    }
}

这是因为内部类直到他们被引用时才会加载。
Java 5中的final语义可以不使用volatile关键字实现安全的创建对象:[6]
public class FinalWrapper<T> {
    public final T value;
    public FinalWrapper(T value) {
        this.value = value;
    }
}
 
public class Foo {
   private FinalWrapper<Helper> helperWrapper = null;
 
   public Helper getHelper() {
      FinalWrapper<Helper> wrapper = helperWrapper;
 
      if (wrapper == null) {
          synchronized(this) {
              if (helperWrapper == null) {
                  helperWrapper = new FinalWrapper<Helper>(new Helper());
              }
              wrapper = helperWrapper;
          }
      }
      return wrapper.value;
   }
}

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

相关推荐

    【Java设计模式-源码】双重检查锁定模式:以最小开销确保线程安全

    双重检查锁定设计模式的目的是通过首先在不实际获取锁的情况下测试锁定条件(“锁提示”)来减少获取锁的开销。只有当锁定条件似乎为真时,实际的锁定逻辑才会继续。Java中的双重检查锁定有助于优化性能并确保线程...

    wikipedia-client, 维基百科API的ruby 客户端.zip

    wikipedia-client, 维基百科API的ruby 客户端 维基百科 允许你通过他们的API获取维基百科内容。 它 将alpha API,不是过时的query.php API类型Wikipedia API参考:http://en.wikipedia.org/w/api.php来自:h

    listen-to-wikipedia, 维基百科编辑中的实时生成音乐.zip

    listen-to-wikipedia, 维基百科编辑中的实时生成音乐 听维基百科维基百科活动的实时可视化与巩固。由 LaPorte 和 Mahmoud Hashemi构建。听维基百科的灵感由 Maximillian 的laumeister BitListen 。我们用声音交换来...

    524MB中文维基百科语料pleisto-json.zip

    524MB中文维基百科语料链接:https://huggingface.co/datasets/pleisto/wikipedia-cn-20230720-filtered 参考这个博客链接进行使用:...

    Python-DrQA阅读维基百科的pytorch实现来回答开放领域的问题

    **Python-DrQA阅读维基百科的PyTorch实现来回答开放领域问题** DrQA(Document Reader and Question Answering)是Facebook AI Research开发的一个开源项目,用于解决机器阅读理解问题,特别是针对开放领域的问答...

    Python-从任何维基百科稳重中导入表单作为Python的数据集

    标题中的“Python-从任何维基百科稳重中导入表单作为Python的数据集”是指使用Python编程语言从维基百科页面中提取表格数据,并将其转换为可处理的Python数据集,通常是一个Pandas DataFrame对象。这一过程涉及到...

    词向量-基于中文维基百科的词向量构建+可视化.zip

    本资料包“词向量-基于中文维基百科的词向量构建+可视化.zip”主要涵盖了如何利用中文维基百科数据生成词向量,并对生成的向量进行可视化展示。 首先,我们要了解词向量的构建方法。其中,最著名的两种算法是Word2...

    swift-UIColor-WikiColors所有维基百科的颜色实现为易于使用的UIColor扩展

    为了简化这个过程,`UIColor-WikiColors`项目提供了一个方便的扩展,将维基百科上列出的所有颜色直接转化为可使用的`UIColor`对象,极大地提高了开发效率。 `swift-UIColor-WikiColors`项目的主要目标是为开发者...

    维基百科中文离线包2020-05

    维基百科中文离线包zim格式,wikipedia_zh_all_maxi_2020-05.zim,使用kiwi打开

    维基百科的下载及阅读方法简单说明(转载)借鉴.pdf

    维基百科下载及阅读方法简单说明 维基百科是一个在线百科全书,提供了丰富的信息资源,内容详实且多样化,对获取事情的真相具有很大的帮助。然而,维基百科上的信息不一定都是正确的,也有一些是不和谐的。加之,...

    维基百科离线版【需自行下载.zim数据库】

    维基百科离线版 一个为了方便本地浏览维基百科查询资料而制作的 开源软件,程序的原理是利用维基百科 kiwix-tools 中的其中一个小工具:kiwix-serve 读取 .zim 格式的维基百科数据库文件,从而实现 http 方式多终端...

    2021-2022年收藏的精品资料wikipedia维基百科综述.ppt

    2021-2022年收藏的精品资料维基百科综述 维基百科是当前流行的网络技术,在网站中的运用非常普遍。在这一类型的网站中,维基百科的规模和影响力都是最大的。维基百科的成功得益于其树立的品牌意识。除此之外,在...

    正态分布 - 维基百科,自由的百科全书.pdf

    正态分布是统计学中非常重要的一个概念,其涵盖了多个数学、物理和工程领域中的应用。作为一种概率分布,正态分布具有独特的数学特征,并且在各种科学和工程问题中扮演着核心角色。正态分布以德国数学家卡尔·...

    2020-4-13-12-46-6--美国县-维基百科.xlsx

    美国二级三级行政区划、包括洲县,县城,县的归属州、成立时间、起源、人口、面积、编码、介绍链接、等信息。

    英文维基百科语料库txt(9)

    标题中的“英文维基百科语料库txt(9)”指的是一个包含多个英文维基百科文本数据的压缩包,这些数据经过一系列预处理步骤,包括分词、去停用词、转换为小写、词干提取和词形还原,使得它们更适合用于自然语言处理...

    中文维基百科hosts文件

    中文维基百科hosts文件,拷贝到C:\Windows\System32\drivers\etc目录下,经测试可使用

    卡尔曼滤波-维基百科

    卡尔曼滤波-维基百科 卡尔曼滤波器在技术上有许多应用。常见的 应用是用于车辆,尤其是飞机和航天器的引 导,导航和控制。 [1] 此外,卡尔曼滤波器是 时间序列分析中广泛应用的概念,用于信号 处理和计量经济学等...

Global site tag (gtag.js) - Google Analytics