并发问题:同步方法

时间:2011-05-17 15:49:02

标签: java concurrency synchronization

2009年11月,我去了No Fluff Just Stuff。其中一个演讲是Brian Goetz,它是关于Java并发的。出于某种原因,他的议程幻灯片中有些项目未在他的演讲中提及。

他讨论了一些策略,最后他指出了一个常见的策略,他也说过对Java中的并发性是一个很好的经验法则:将变量设为私有,并使任何访问它们的方法同步。

听起来很简单。也许真是太好了。是否存在这种并发技术不足的情况/应用?是否主要依赖这种技术在具有大量事务或大型数据集的系统中运行良好?这样做的潜在缺点是什么?

10 个答案:

答案 0 :(得分:4)

  

是否存在这种并发技术不足的情况/应用?

如果您有一个想要同步的操作,它跨越了其中几个方法,那么将每个单独的方法标记为synchronized是不够的。

  

主要依靠这种技术在具有大量事务或大型数据集的系统中是否能够很好地工作?

我对此表示怀疑,因为操作的同步边界似乎很少是这样。

  

这样做的潜在缺点是什么?

同步不是免费的,获得锁定涉及成本(尽管通常很小)。不必要的同步意味着不必要的成本。

坦率地说,在阅读 Java Concurrency in Practice 后,我很惊讶Goetz会给出这个建议,因为该书的主要教训是并发是一个复杂的主题,解决方案是使您的应用程序线程安全并且具有并发访问权限的性能是针对应用程序的细节高度自定义的,并且没有简单快速的答案。

答案 1 :(得分:2)

虽然对并发编程不熟悉,但我不认为在并发方面有一个灵丹妙药'让所有成员都成为私有且方法同步'。

查看:http://download.oracle.com/javase/tutorial/essential/concurrency/index.html

此外,对于共享数据(可由多个线程访问和/或修改的数据),您可能强制使用同步方法。在其他情况下,您可能不必使用synchronized关键字。

答案 2 :(得分:1)

除了性能影响之外,还存在这种保护水平不足的情况。例如,setX(),setY()。如果您的API分别具有这些,那么如果一个线程设置x1,y1而另一个线程设置x2,y2,则最终可以得到x1,y2。为避免这种情况,您需要在调用setter之前锁定对象,或重新设计API以支持setXY(x,y)。

答案 3 :(得分:1)

在许多情况下,这绝对不够。考虑你想要切换Vector的两个元素的位置的情况(基本上遵循Goetz所描述的策略)。您的代码将包含对removeElementinsertElementAt的一系列调用(两者都是同步的)。但是,如果Vector对象被这些调用之间的另一个线程修改,则结果可能完全是垃圾。

虽然Goetz认为这是一种常见的策略,但通常需要更高级别的同步。这就是为什么引入ArrayList的原因 - 它基本上是一个Vector,没有方法级同步的(通常是不必要的)开销。

答案 4 :(得分:1)

这似乎就像一些严重的矫枉过正。对于递增/获取序列来说,这也是不够的,其中另一个线程可能会交错进行set()调用。

如果我遇到过每个对象同步getter / setter方法的代码库,我会对编码器产生严重怀疑 - 我很可能认为他们最近才读到同步并且没有完全理解它

答案 5 :(得分:1)

在[matt b]引用的Goetz的书中给出了一个明显的例子。

将资金从帐户A转移到B:

synchronized (a) {
    synchronized (b) {
        // do the transfer
    }
}

看起来很清楚,但如果两个线程同时尝试将$ 1从A转移到B而另一个$ 2从B转移到A并且时间片发生在同步块之间会发生什么呢?

一种解决方案是订购它们并首先锁定较小的帐号。这本书有更多的例子和解决方案。所以不,没有简单的答案,但make private和synchronize成员访问将足以满足许多应用程序的需求。

答案 6 :(得分:0)

“将您的变量设为私有,并使任何访问它们的方法同步。” - 本身就是合理的建议,可以在某些非常基本的情况下使代码保持线程安全。

然而,绝对不足以确保在更复杂的情况下正确的并发操作 - 例如您需要在单个事务中一次修改两个不同的对象。通常,当您尝试将两个或多个锁定/同步操作粘合在一起时,会出现“锁定不构成”的问题。

如果您对并发和可变状态感兴趣,我强烈建议您观看此presentation on "Value, Identity and State" by Rich Hickey。它讨论了Clojure并发系统的设计,但这些原则很重要,适用于任何语言。

答案 7 :(得分:0)

该技术生成线程安全的代码,因为它保护您的java变量免受来自多个线程的读/写中断。但是,这并不意味着您的代码是正确的。有时,同步适用于单个方法无法很好表示的执行序列。想象一个接受矩形并将宽度和长度设置为等于5的例程。想象一个不同线程上的另一个例程,它接受一个矩形并将宽度设置为3,长度等于6.尽管setWidth和setLength是同步的,线程一可以设置宽度,线程二可以设置宽度和长度,线程一可以设置长度。现在矩形的宽度为5,长度为6.根据任一线程,这个都不正确。注意,如果矩形是不可变的,那么这个特殊问题就不会发生。

这是大型系统中出现的一个例子。想象一下,您需要在两台计算机上同步两个文件的分布式系统。您需要在每个文件上获得某种同步锁定。如果许多不同的线程争用不同的文件,则需要一种机制来确定谁获得锁定。有很多方法可以解决这个问题,因此它不是一个未知的问题,但是您可以看到它不仅仅是单个对象中的两个私有变量一样简单。

现在,您的后续行动:有哪些缺点?如果您有不可变资源,则可能不需要防止不同线程的多次读取。结果,同步代码的额外开销是不必要的。您的程序虽然正确,但由于不必要的同步而比使用相同算法实现的另一个正确程序慢。

答案 8 :(得分:0)

我怀疑他会推荐这种方法,因为它确实存在缺陷并且可能导致比解决方案更多的问题。但如果他按照你描述的方式推荐它,他就错了。

正如其他人已经提到的,这种方法不提供原子性(多个操作不是原子地执行的)。这是一个重大问题。

想到的其他一些可能的问题:

  • 所有成员私有和所有方法 同步?包括安装者 和吸气剂?那只是坏,不好, 坏。
  • 那些性能非常重要并且您的读取会阻止/实现写入和其他读取的情况怎么样?这也很糟糕,更好的选择是使用像ReentrantReadWriteLock这样的东西。
  • 容易出现死锁,请参阅karmakaze提供的示例

但对此最危险的是,有人可能会把它看作是一把瑞士军刀,但显然不是。有用的要记住:synchronized从一开始就是Java的一部分,为什么还要烦扰所有的替代品呢?有一点必须明确:出于性能原因,我们现在拥有所有这些其他功能:

  • 锁定课程
  • 大量并发课程
  • 适用于Java 1.5的适当的volatile和一个不错的JMM

我的推荐?尝试尽可能多地学习Java中的并发性,有很多资源,谷歌(或任何体面的SE)是你最好的朋友。只是准备花一些时间,这并不容易,如果你真的想了解兔子洞里发生了什么,它会变得非常深。今天和未来的CPU越来越多的线程执行单元[将]使并发技能“必须”,所以没有理由跳过这个。

答案 9 :(得分:0)

我很确定我知道您正在考虑哪种演示文稿,并且您跳过了很多步骤。

该演示文稿中的建议是:   - 封装你的州。 (这里没有任何争议)。   - 在您需要同步访问状态的范围内,封装该同步,也就是说,在您的客户端上导出同步要求是一个错误的调用。

从这里开始“使所有访问它们的变量同步”是跳过分析哪些变量被共享以及用于同步访问该状态的逻辑策略,并向右跳转到“同步所有内容”。