安全发布 Java并发编程实战总结

        到目前为止, 我们重点讨论的是如何确保对象不被发布, 例如让对象封闭在线程或另一个对象的内部。 当然,在某些情况下我们希望在多个线程间共享对象, 此时必须确保安全地进行共享。 然而, 如果只是像程序清单 3-14 那样将对象引用保存到公有域中, 那么还不足以安全地发布这个对象。


        你可能会奇怪, 这个看似没有问题的示例何以会运行失败。由于存在可见性问题, 其他线程看到的Holder 对象将处于不一致的状态, 即便在该对象的构造函数中已经正确地构建了不变性条件。这种不正确的发布导致其他线程看到尚未创建完成的对象。

不正确性的发布

        你不能指望一个尚未被完全创建的对象拥有完整性。某个观察该对象的线程将看到对象处于不一致的状态, 然后看到对象的状态突然发生变化, 即使线程在对象发布后还没有修改过它。 事实上, 如果程序清单3-15中的Holder使用程序清单3-14中的不安全发布方式, 那么另一个线程在调用assertSanity时将抛出AssertionError 。


        由于没有使用同步来确保Holder对象对其他线程可见, 因此将Holder称为“ 未被正确发布”。在未被正确发布的对象中存在两个问题。首先, 除了发布对象的线程外, 其他线程可以看到的Holder域是一个失效值, 因此将看到一个空引用或者之前的旧值。然而, 更糟糕的情况是, 线程看到Holder引用的值是最新的, 但Holder状态的值却是失效的。情况变得更加不可预测的是, 某个线程在第一次读取域时得到失效值, 而再次读取这个域时会得到一个更新值,这也是assertSainty抛出AssertionError的原因。

        如果没有足够的同步, 那么当在多个线程间共享数据时将发生一些非常奇怪的事情。

不可变对象与初始化安全性

        由于不可变对象是一种非常重要的对象, 因此Java内存模型为不可变对象的共享提供一种特殊的初始化安全性保证。我们已经知道, 即使某个对象的引用对其他线程是可见的,也并不意味着对象状态对于使用该对象的线程来说一定是可见的。为了确保对象状态能呈现出一致的视图, 就必须使用同步

        另一方面,即使在发布不可变对象的引用时没有使用同步, 也仍然可以安全地访问该对象。为了维持这种初始化安全性的保证, 必须满足不可变性的所有需求:状态不可修改, 所有域都是final类型, 以及正确的构造过程。(如果程序清单3-15中的Holder对象是不可变的,那么即使Holder 没有被正确地发布, 在assertSanity中也不会抛出Asserti.onError。)

        任何线程都可以在不需要额外同步的情况下安全地访问不可变对象,即使在发布这些对象时没有使用同步。

        这种保证还将延伸到被正确创建对象中所有 final 类型的域。在没有额外同步的情况下,也可以安全地访问 final类型的域。然而,如果final 类型的域所指向的是可变对象,那么在访问这些域所指向的对象的状态时仍然需要同步。

安全发布的常用模式

        可变对象必须通过安全的方式来发布,这通常意味若在发布和使用该对象的线程时都必须使用同步。现在,我们将重点介绍如何确保使用对象的线程能够看到该对象处于已发布的状态,并稍后介绍如何在对象发布后对其可见性进行修改。

        要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:

a.在静态初始化函数中初始化一个对象引用。

b.将对象的引用保存到volatile 类型的域或者AtomicReferance 对象中。

c.将对象的引用保存到某个正确构造对象的final 类型域中。

d.将对象的引用保存到一个由锁保护的域中

        在线程安全容器内部的同步意味着,在将对象放入到某个容器,例如Vector 或synchronizedList时,将满足上述最后一条需求。如果线程A将对象X放入一个线程安全的容器,随后线程B读取这个对象,那么可以确保B看到A设置的X 状态,即便在这段读/写X的应用程序代码中没有包含显式的同步。尽管Javadoc 在这个主题上没有给出很清晰的说明,但线程安全库中的容器类提供了以下的安全发布保证:

a.通过将一个键或者值放入Hashtable、synchronizedMap 或者ConcurrentMap 中,可以安全地将它发布给任何从这些容器中访问它的线程(无论是直接访问还是通过迭代器访问。

b.通过将某个元素放入Vector 、CopyOnWriteArrayList、CopyOnWriteArraySet 、synchronizedList或synchronizedSet 中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程。

c.通过将某个元素放入BlockingQueue 或者ConcurrentLinkedQueue 中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程。

        类库中的其他数据传递机制(例如Future 和Exchanger) 同样能实现安全发布,在介绍这些机制时将讨论它们的安全发布功能。

        通常,要发布一个静态构造的对象,最简单和最安全的方式是使用静态的初始化器:

public static Holder holder= new Holder(42);

        静态初始化器由JVM在类的初始化阶段执行。由于在JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布[JLS 12.4.2] 。

事实不可变对象

        如果对象在发布后不会被修改, 那么对于其他在没有额外同步的情况下安全地访问这些对象的线程来说, 安全发布是足够的。所有的安全发布机制都能确保, 当对象的引用对所有访问该对象的线程可见时, 对象发布时的状态对于所有线程也将是可见的, 井且如果对象状态不会再改变, 那么就足以确保任何访问都是安全的。

        如果对象从技术上来看是可变的, 但其状态在发布后不会再改变, 那么把这种对象称为“事实不可变对象(Effectively Immutable Object)"。这些对象不需要满足3.4节中提出的不可变性的严格定义。在这些对象发布后, 程序只需将它们视为不可变对象即可。通过使用事实不可变对象, 不仅可以简化开发过程, 而且还能由于减少了同步而提高性能。

        在没有额外的同步的情况下,任何线程都可以安全地使用被安全发布的事实不可变对象。

可变对象

        如果对象在构造后可以修改, 那么安全发布只能确保“发布当时” 状态的可见性。对于可变对象, 不仅在发布对象时需要使用同步, 而且在每次对象访问时同样需要使用同步来确保后续修改操作的可见性。要安全地共享可变对象, 这些对象就必须被安全地发布, 并且必须是线程安全的或者由某个锁保护起来。


安全地共享对象

        当获得对象的一个引用时, 你需要知道在这个引用上可以执行哪些操作。在使用它之前是否需要获得一个锁?是否可以修改它的状态, 或者只能读取它?许多并发错误都是由于没有理解共享对象的这些“既定规则”而导致的。当发布一个对象时,必须明确地说明对象的访问方式。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容