Java基于锁并发和基于无锁并发的比较图示。试图解决在一个系统中并发读共享状态时发生的争用问题。StampedLock设计用来优化读性能,它的性能要优于ReentrantReadWriteLock。
第一,在我看完Java现在对锁的实现,我想这是一个关于时间的问题。第二,虽然StampedLock是JDK中的一个非常好的实现,但是这是没有考虑无锁并发算法的情况下。
测试用例
为了比较各自的实现,我需要设计一套API作为测试用例,这个API和用例是相对公平的。例如,这套API必须足够纯洁,不存在消耗时间和性能的累赘,方法是原子性的。一个简单的测试用例是设计一个Spaceship(宇宙飞船)在一个二维空间中移动,它的位置是原子读的。至少有两个属性被读或者被写,并发是事务性的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/** * Interface to a concurrent representation of a ship that can move around * a 2 dimensional space with updates and reads performed concurrently. */ public interface Spaceship { /** * Read the position of the spaceship into the array of coordinates provided. * * @param coordinates into which the x and y coordinates should be read. * @return the number of attempts made to read the current state. */ int readPosition( final int [] coordinates); /** * Move the position of the spaceship by a delta to the x and y coordinates. * * @param xDelta delta by which the spaceship should be moved in the x-axis. * @param yDelta delta by which the spaceship should be moved in the y-axis. * @return the number of attempts made to write the new coordinates. */ int move( final int xDelta, final int yDelta); } |
上面的API可以整理提取出一个Position的对象,但是我想消除垃圾产生,还有更直接的使用属性。这套API很容易可以扩展为3维空间并且要求实现的原子性。
现在,我实现不同的Spaceship,这些实现可以运行在一套测试套件中。所有的实现的源代码可以通过这个链接得到。测试套件将运行每一个实现。
每个实现都会测试用不同的线程在4种场景下面进行测试并比较结果,下面是测试用例的说明:
1、1个读线程 – 1个写线程。
2、2个读线程 – 1个写线程。
3、3个读线程 – 1个写线程。
4、2个读线程 – 2个写线程。
所有的测试用例都跑在64位 Java 1.7.0_25, Linux 3.6.30,i7-3632QM 4核2.2G的环境上。如下的吞吐量测量结果是通过5次运行的每秒平均值。为了接近与Java特有的部属环境,这里保证没有线程关联,保证内核隔离。
注意:不同类型的CPU和操作系统可能产生不一样的结果。
结果比较
生成场面图表的数据可以这里下载。
分析
让我感到惊讶的是ReentrantReadWriteLock的性能。我无法想象使用这个实现的在所有用例中的读如此接近,写性能如此低。我的主要想法如下:
1、StampedLock是有很大的提升,除了在读线程增加的情况下,它的性能超过了其他锁实现。
2、StampedLock拥有复杂的API,很容易的锁条件的时候调用了错误的方法。
3、Synchronised在两个线程竞争的情况下,是一个很好的通用实现。
4、线程数量递增的情况下,ReentrantLock是一个很好的通用实现。
5、选择使用ReentrantReadWriteLock的时候,要仔细观察它的适用场景。在使用度量和数据下面做出决定。
6、无锁实现的吞吐量要明显由于基于锁的实现。
结论
很高兴无锁算法的表现要优于锁算法,使用无锁算法是提供高读写性能优化的一种很好的策略。
在我写这个文章还有这个文章的相关代码的经验,我发现无锁算法不只可以提高平均吞吐量,也可以降低延迟并且保证延迟时间的分布均匀。