文章目录
  1. 1. 测试原理
  2. 2. 使用方式
  3. 3. 测试结果
  4. 4. 结论
  5. 5. 建议
  6. 6. 参考

Java提供了多种同步机制,比如AtomicLong、LongAdder、ReentrantLock等,但是他们的性能到底如何?网上也有一些性能测试文章,但是要么同步机制不全,要么场景不全,因此笔者设计了一个性能测试框架,支持多场景、多种同步机制的性能测试,代码请参见locksbenchmark,后续有时间了再通过JMH进行对比测试。

测试原理

既然是同步测试,当然要使用多线程才行,而且最好覆盖多种场景,总结起来本次测试有以下几个关键点:

  • 可以指定读、写线程数,来模拟读多写少、读少写多,读写等比等场景。
  • 通过CyclicBarrier保证,所有线程开始工作那一刻才启动计时。通过CountDownLatch保证,只要有一个线程达成目标,就结束计时,保证计时尽量准确。
  • 每种同步机制,经过多轮测试,结果去掉最小值、最大值然后取平均值,避免JVM未预热出现奇异值。
  • 同步代码块中的代码非常简单,就是累加long型数值到一个目标值。如果需要测试新的同步机制,只需要扩展Counter类即可,具体实现可以参照类Raw

使用方式

代码托管在github,使用maven工具构建,具体命令如下:

1
2
3
4
5
$ git clone git@github.com:chenpy228/locksbenchmark.git
$ cd locksbenchmark
$ mvn clean package
$ java -jar target/locksbenchmark-1.0.jar -h # add -h to show help info
$ java -jar target/locksbenchmark-1.0.jar # begin to test by using default parameter

测试结果

测试环境是Ubuntu 14.04.5 LTS,内核版本是4.4.0-101-generic,CPU是2核,JVM信息如下:

1
2
3
4
$ java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)

最终的测试结果如下,为了方便对比,Raw为没有使用任何同步机制的测试结果:
results

由于部分同步机制的运行时间相差太大,所以对坐标轴进行了对数化(log10)处理,绘制图表如下(越短越好):
results

去掉公平锁和读写锁后绘图如下(越短越好):
results without fair and rw lock

结论

  • 公平锁ReentrantLock(true),即上图中的FairReentrantLock,在所有场景下的表现都比较差,所以在实际应用中很少看到它的身影。如果必须要用,一定要经过谨慎测试。
  • 读写锁ReentrantReadWriteLock,即上图中的RWLock,在读多写少的场景下表现的比较差,这跟我们的直觉可能不符,所以在java8中又引入了升级版的读写锁StampedLock。当然在写多读少的情况下,表现比较好,基本跟StampedLock持平。
  • 在所有场景下,AtomicLongLongAdder和非公平锁ReentrantLock(false)的表现相差不大,而且AtomicLong略有优势。按照文档的说法,只有竞争非常激烈的时候,LongAdder才更有优势,读写比20:20估计还不是竞争激烈的时候,针对这两个类补充测试了读写比为200:200场景,结果显示LongAdder更胜一筹。目前来看,用AtomicLong可以满足需求,而且个人认为AtomicLong的接口更丰富。
  • 再来看看StampedLock,在读多写少的场景下,它的乐观锁(即上图中的OptimisticStampedLock)确实更胜一筹,其他场景下悲观锁略好一些。
  • 最后看synchronized,整体跟StampedLock表现差不多。
  • 基本上,AtomicLongLongAdder属于第一梯队,synchronizedStampedLock和非公平锁ReentrantLock(false)属于第二梯队,ReentrantReadWriteLock和公平锁ReentrantLock(true)属于第三梯队。

建议

如果被保护的是基本数据类型,在大多数场景下,建议使用Atomic*系列锁,如果竞争非常激烈可以使用LongAdder。如果被保护的是复杂的数据结构,在读多写少的场景下,可以用StampedLock的乐观锁,在读少写多的场景下,可以用StampedLock的悲观锁,其他场景直接用ReentrantLock的非公平锁即可。

参考

文章目录
  1. 1. 测试原理
  2. 2. 使用方式
  3. 3. 测试结果
  4. 4. 结论
  5. 5. 建议
  6. 6. 参考