上周在由Heinz Kabutz经过JCrete 组织的开放空间会议(unconference)上,我参加一个新的java规范 JSR166 StampedLock 的审查会议。 StampedLock 是为了解决多个readers 并发访问共享状态时,系统出现的内存地址竞争问题。在设计上经过使用乐观的读操做, StampedLock
比 ReentrantReadWriteLock 更加高效;html
在会议期间,我忽然意思到两点:java
为了比较不一样的实现方式,我须要采用一种不偏向任意一方的API测试用例。 好比:API必须不产生垃圾、而且容许方法是原子性的。一个简单的测试用例是设计一个可在两维空间中移动其位置的太空船,它位置的坐标能够原子性的读取;每一次事物里至少须要读写2个域,这使得并发变得很是有趣;git
/** * 并发接口,表示太空船能够在2维的空间中移动位置;而且同时更新读取位置 */ public interface Spaceship { /** * 读取太空船的位置到参数数组 coordinates 中 * * @param coordinates 保存读取到的XY坐标. * @return 当前的状态 */ int readPosition(final int[] coordinates); /** * 经过增长XY的值表示移动太空船的位置。 * * @param xDelta x坐标轴上移动的增量. * @param yDelta y坐标轴上移动的增量. * @return the number of attempts made to write the new coordinates. */ int move(final int xDelta, final int yDelta); }
上面的API经过分解一个不变的位置对象,自己是干净的。可是我想保证它不产生垃圾,而且须要能最直接的更新多个内容域。这个API能够很容易地扩展到三维空间,并实现原子性要求。github
为每个飞船都设置多个实现,而且做为一个测试套件。本文中全部的代码和结果均可以在这里找到。算法
该测试套件会依次运行每一种实现.而且使用 megamorphic dispatch模式,防止并发访问中的方法内联(inlining),锁粗化(lock-coarsening),循环展开(loop unrolling)的问题;segmentfault
每种实现都执行下面4个不一样的线程的状况,结果也是不一样的;api
1 reader – 1 writer 2 readers – 1 writer 3 readers – 1 writer 2 readers – 2 writers
全部的测试运行在64位机器、Java版本:1.7.0_2五、 Linux版本:3.6.30、4核 2.2GHz Ivy Bridge (第三代Core i系列处理器)i7-3632QM的环境上。数组
测试吞吐量的时候,是经过每一种实现都重复测试超过5次,每一次都运行5秒以上,以保证系统足够预热,下面的结果都是第5次以后平均每秒吞吐量。为了更像一个典型的java应用;没有采用会致使明显减小差别的线程依附性(thread affinity)和多核隔离(core isolation )技术;并发
上述图表的原始数据能够在这里找到oracle
结果里面真正令我吃惊的是ReentrantReadWriteLock的性能,我没有想到的是,在这样的场景下它在读和少许写之间取得的巨大的平衡性,
我主要的收获:
StampedLock 对现存的锁实现有巨大的改进,特别是在读线程愈来愈多的场景下:
StampedLock有一个复杂的API,对于加锁操做,很容易误用其余方法;
当只有2个竞争者的时候,Synchronised是一个很好的通用的锁实现;
当线程增加可以预估,ReentrantLock是一个很好的通用的锁实现;
选择使用ReentrantReadWriteLock时,必须通过当心的适度的测试;全部重大的决定,必须在基于测试数据的基础上作决定;
无锁的实现比基于锁的算法有更好短吞吐量;
很是开心能看到无锁技术对基于锁的算法的影响; 乐观锁的策略,实际上就是一个无锁算法技术。
以个人经验看,教学和开发中的无锁算法,不只能显著改善吞吐量;同时他们也提供更低的延迟。
原文 Lock based vs lock free concurrent
翻译 曹姚君 校对 方腾飞
via ifeve