天天看点

建议:避免过度同步。

依据情况的不同,过度同步可能会导致性能降低、死锁,甚至不确定的行为。

为了避免活性失败和安全性失败,在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制。换句话说,在一个被同步的区域内部,不要调用设计成要被覆盖的方法,或者是由客户端以函数对象的形式提供的方法。从包含该同步区域的类的角度来看,这样的方法是外来的。这个类不知道该方法会做什么事情,也无法控制它。根据外来方法的作用,从同步区域中调用他会导致异常、死锁或者数据损坏。

假设当同步区域所保护的约束条件暂时无效时,你要从同步区域中调用一个外来方法。由于Java程序设计语言中的锁是可重入的,这种调用不会死锁。他会产生一个异常,因为调用线程已经有这个锁了,因此当该线程试图再次获得该锁时会成功,尽管概念上不相关的另一项操作正在该锁保护的数据上进行着。这种失败的后果可能是灾难的。从本质上来说,这个锁没有尽到它的职责。可再入的锁简化了多线程的面向对象程序的构造,但是他们可能会将活性失败变成安全性失败。

事实上,要将外来方法的调用移出同步的代码块。还有一种更好地方法。自从Java 1.5发行版本以来,Java类库就提供了一个并发集合,称作CopyOnWriteArrayLIst,这是专门为此定制的。这是ArrayList的一种变种,通过重新拷贝整个底层数组,在这里实现所有的写操作。由于内部数组永远不改动,因此迭代不需要锁定,速度也非常快。如果大量使用,CopyOnWriteArrayList的性能将大受影响,但是对于观察者列表来说却是很好的,因为他们几乎不改动,并且经常被遍历。

在同步区域之外被调用的外来方法被称作“开放调用”。除了可以避免死锁之外,开放调用还可以极大地增加并发性。外来方法的运行时间可能会任意长。如果在同步区域内调用外来方法,其他线程对受保护资源的访问就会遭到不必要的拒绝。

通常,你应该在同步区域内做尽可能少的工作。获得锁,检查共享数据,根据需要转换数据,然后放掉锁。如果你必须要执行某个很耗时的动作,则应该设法把这个动作移到同步区域的外面。

虽然自从Java平台早期以来,同步的成本已经下降了,但更重要的是,永远不要过度同步。在这个多核的时代,过度同步的实际成本并不是指获取锁所花费的CPU时间;而是指失去了并行的机会,以及因为需要确保每个核都有一个一致的内存视图而导致的延迟。过度同步的另一项潜在开销在于,他会限制VM优化代码执行的能力。

如果一个可变的类要并发使用,应该使这个类变成是线程安全的,通过内部同步,你还可以获得明显比从外部锁定整个对象更高的开发性。否则,就不要在内部同步。让客户在必要的时候从外部同步。在Java平台出现的早期,许多类都违背了这些指导方针。当你不确定的时候,就不要同步你的类,而是应该建立文档,注明它不是线程安全的。

如果你的内部同步了类,就可以使用不同的方法来实现高并发性,例如分拆锁、分离锁和非阻塞并发控制。

如果方法修改了静态域,那么你也必须同步对这个域的访问,即使他往往只用于单个线程。客户要在这种方法上执行外部同步时不可能的,因为不可能保证其他不想关的客户也会执行外部同步。

简而言之,为了避免死锁和数据破坏。千万不要从同步区域内部调用外来方法。更为一般的讲,要尽量限制同步区域内部的工作量。当你在设计一个可变类的时候,要考虑一下他们是否应该自己完成同步操作。在现在这个多核的时代,这比永远不要过度同步来得更重要。只有当你有足够的理由一定要在内部同步类的时候,才应该这么做,同时还应该将这个决定清楚地写到文档中。