以线程安全的方式发布非线程安全对象字段

8
我在Java并发方面遇到了问题。是的,我看过几乎相同标题的问题,但它们似乎都在询问微妙的不同之处。是的,我读过《Java并发编程实战》。是的,我能理解为什么它是该主题的事实上的参考书。是的,我已经阅读了特别介绍线程安全类中字段发布的部分。但是,尽管我知道有人会简单地指向那本书,我仍然要在Java上提出一个并发问题。
但这让我困惑了 - 我知道你可以通过确保正确的读/写顺序使用易失性和/或同步访问来轻松地以线程安全的方式发布可变原始字段,并且由于其读/写操作的缺乏原子性,64位原语需要具有原子访问。我知道关于在需要在类的字段的特定“快照”上执行的代码块上使用锁定。我完全了解具有诸如AtomicLong<>等好处的原子包。
但是,对于在线程安全类中作为字段发布非线程安全对象仍感到困惑。
据我所见,一旦您在getter中返回对它的引用,您就已经向调用者提供了对对象内容的无限制访问,他们可以在任何时候使用它。此外,如果您提供了setter,则允许他们将对象引用设置为可能在使用setter的对象之外控制的对象。
我无法想出一种将非线程安全对象组合成线程安全类的方法,而不需要使它们全部变为私有/受保护,并为类中用户可能想要使用的所有非线程安全对象的方法创建线程安全包装方法。这听起来就像是一个样板文件噩梦。
我的意思是,如果您在getter中返回AtomicReference<>以获取对象,则可以使用.get()再次获得非同步访问。
我考虑的另一种方式是让所有getter基于旧值返回非线程安全对象的新副本,这意味着修改将是无关紧要的,setter也是如此。但是,Java具有一个令人绝望的对象克隆系统(浅拷贝vs深拷贝vs特定复制等),这让我对此感到厌烦。此外,这太低效了,与像Clojure这样“设计”为不可变性的语言相比,它不会更快。实际上,鉴于这些语言允许多个不可变数据共享背后的相同数据,它可能会慢得多。
那么,如何以可行的方式组合发布的非线程安全对象的线程安全类?
提前感谢。
1个回答

4
如果对不安全对象的引用逃逸到周围线程 - 你无法阻止其他线程改变状态,因此你应该保持引用的安全性。将数据设为私有,添加封装访问和修改的方法,并创建线程安全的副本(是的,克隆很麻烦),如果你需要返回复杂对象。
尝试看看http://en.wikipedia.org/wiki/Law_of_Demeter设计原则。 引用: 特别是,一个对象应该避免调用另一个方法返回的成员对象的方法。对于许多使用点作为字段标识符的现代面向对象语言,该规则可以简单地陈述为“只使用一个点”。也就是说,代码a.b.Method()违反了这个规则,而a.Method()没有违反。作为一个简单的例子,当你想遛狗时,直接命令狗的腿走路是愚蠢的;相反,你命令狗,让它自己照顾好自己的腿。
附注:恐怕这是一个开放式问题。

感谢澄清。因此,对于需要执行的对象字段上的每个单独操作,我都需要提供一个新的线程安全方法。我想,组成另一个对象的对象通常只需要对象字段功能的子集,因此它不是_那么_多的样板文件。所有这些都让我渴望D的可传递不变性...有点像C++的引用到常量,但更加彻底。 - Louis
我假设这排除了从非线程安全类继承,除非你打算用线程安全的替代方法覆盖每个方法。即使如此,如果父类的作者添加了新的非线程安全方法,那么你的类将继承它们,因此也会变得非线程安全。幸好我更喜欢对象组合。 - Louis
是的,继承是一个容易被滥用的领域,如果你使用继承,你就会面临一些意外。Joshua Bloch是Java对象设计和编码问题领域的著名作者。我向所有同事推荐他的书《Effective Java》。他说要么为继承记录你的类,要么禁止它并将你的类设为final。链接:文章 幻灯片 演讲视频 书籍 - ya_pulser
在我做的一个小项目中,我将所有的类都设置为final,因为它们都不是为了继承而设计的。当时我认为这是一种非常糟糕的做法,但现在你指出其他人也这样做,这让我感觉好多了 :) 但是当我完全排除继承设计时,一切变得更加简单。哦,我猜想不继承和不允许继承意味着你不再需要在每个方法调用时遭受vtable查找的困扰了。 - Louis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接