才华能力出众的ReentrantLock (2)

Tim Peierls(《Java并发编程实战》作者)使用简单的线性同余伪随机数生成器(PRNG)构建了一个简单的基准,用于测量synchronized与Lock的相对可伸缩性。
这个示例很好,因为每次调用nextRandom()时,PRNG实际上都会做一些实际工作,因此该基准测试是合理的,符合实际应用的,而不是通过睡眠计时模拟或不做任何事情。

在此基准测试中,我们有一个PseudoRandom接口,接口中只有一个方法nextRandom(int bound)。该接口与java.util.Random类的功能非常相似。
因为PRNG将上一次生成的数作为下一次生成随机数的输入,并且将上一次生成的数作为实例变量进行维护,所以很重要的一点是,更新该变量的代码块不能被其他线程抢占,
因此我们需要某种形式的锁来确保这一点。 (java.util.Random也是这么做的)
我们分别用ReentrantLock和synchronized实现了两个PseudoRandom。主程序会产生许多线程,每个线程都疯狂地掷骰子,然后计算不同版本每秒能够掷多少次骰子。
图1和图2中是不同线程数下的测试结果。
该基准测试并不完美,它仅在两个系统上运行(具有超线程的dual Xeon运行Linux,一个单处理器Windows系统),但应该足以表明ReentrantLock比同步具有更好的可伸缩性。
Figure 1. Throughput for synchronization and Lock, single CPU

才华能力出众的ReentrantLock

Figure 2. Throughput (normalized) for synchronization and Lock, four CPUs

才华能力出众的ReentrantLock


图1和图2显示了两种实现的每秒吞吐量(已标准化为1个线程同步的情况)。
可以看到,两种实现在稳态吞吐量(steady-state)上都相对较快地收敛,这通常意味着处理器已得到充分利用。
你也许已经注意到,无论哪种情况的竞争,synchronized版本的性能都会显著恶化,而Lock版本在调度开销上花费的时间要少得多,从而为更高的吞吐量和更有效的CPU利用率腾出了空间。

Condition变量

根对象(Object类)中包括一些用于跨线程通信的特殊方法-wait(), notify(), notifyAll()。
这些是高级的并发功能,很多开发人员未曾使用过它们,不过这可能是好事,因为他们的工作机制非常微妙而且容易错误使用。
幸运的是,在JDK5.0中添加了java.util.concurrent后,开发人员能使用到这些方法的情况就更少了。
notify和wait之间存在互相作用,要在一个对象上wait或notify,你必须要持有该对象的monitor(锁)。
就像Lock是synchronized的泛化一样,Lock框架中也有notify和wait的泛化,称为Condition。
Lock对象充当了把Condition变量绑定到锁的工厂对象。与标准的wait和notify方法不同,可以给一个Lock绑定多个Condition变量。
这简化了许多并发算法的开发。例如,在Condition的JavaDoc中展示了一个例子,使用两个条件变量实现一个有界缓冲区,"not full"和 "not empty"。
与每个锁上只有一个等待集(wait set)相比,条件变量更易读且更有效。
类似于wait,notify和notifyAll,Condition的方法被命名为await,signal和signalAll,因为它们无法覆盖Object中的相应方法。

这是不公平的

如果你仔细阅读过Javadoc,会发现ReentrantLock的构造函数中有一个布尔类型的参数,让你选择是需要公平锁还是非公平锁。
公平锁是指线程得到锁的顺序与请求锁的顺序相同,先来先得。非公平锁可能会允许插入,其中某个线程可能会先于其他更早请求锁的线程得到锁。
为什么我们不希望锁都是公平的?毕竟公平是件好事,不公平是坏事,对吧?
实际上,公平锁是非常重的,并且付出了巨大的性能成本。公平即意味着比非公平锁更低的吞吐量。
默认情况下,你应该选择非公平锁,除非你的算法对正确性有严苛的要求,线程必须按照它们排队的顺序执行。

那么synchronized呢?内置的monitor锁是公平的吗?答案是,可能让你很惊讶,他们不是,从来都不是。
没有人会抱怨线程饥饿问题,因为JVM保证了所有正在等待锁的线程最终都会获取锁。
大多数情况下,保证统计学上的公平性已经足够了,而且其成本要比保证绝对公平性要低得多。
因此,默认情况下,ReentrantLock是“不公平的”,和synchronized保持一致。
图3和图4的基准测试与上面的图1、图2是一样的,只是增加了一个公平锁(FAIR)。如你所见,“公平”不是免费的。 所以不要把“公平”作为默认值。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpspxj.html