性能对比:ReentrantLock vs Synchronized

栏目: 编程工具 · 发布时间: 5年前

内容简介:最近遇到高并发引起的性能问题,最终定位到的问题是压测在一个 40 个核的机器上,tomcat 默认 200 个线程,发送方以 500 并发约 1w QPS 发送请求,要求999 分位的响应在 50ms 左右。代码中有一个异步写入数据库的任务,实际测试时有超过 60% 的延时都在写入队列中(实际上是往 ThreadPool 提交任务)。于是开始调研LinkedBlockingQueue 相当于是普通 LinkedList 加上

最近遇到高并发引起的性能问题,最终定位到的问题是 LinkedBlockingQueue 的性能不行,最终通过创建了多个 Queue 来减少每个 Queue 的竞争压力。人生中第一次遇到 JDK 自带数据结构无法满足需求的情形,决心好好研究一下为什么。

压测在一个 40 个核的机器上,tomcat 默认 200 个线程,发送方以 500 并发约 1w QPS 发送请求,要求999 分位的响应在 50ms 左右。代码中有一个异步写入数据库的任务,实际测试时有超过 60% 的延时都在写入队列中(实际上是往 ThreadPool 提交任务)。于是开始调研 LinkedBlockingQueue 的实现。

LinkedBlockingQueue 相当于是普通 LinkedList 加上 ReentrantLock 在操作时加锁。而 ReentrantLock (以及其它 Java 中的锁)内部都是靠 CAS 来实现原子性。而 CAS 在高并发时因为线程会不停重试,所以理论上性能会比原生的锁更差。

实际上想对比 CAS 和原生锁是很困难的。Java 中没有原生的锁,而 synchronized 有 JDK 的各种优化,在一些低并发的情况下也用到了 CAS。对比过 synchronizedUnsafe.compareAndSwapInt 发现 CAS 被吊打。所以最后还是退而求其次对比 ReentrantLockSynchronized 的性能。

一个线程竞争 ReentrantLock 失败时,会被放到等待对列中,不会参与后续的竞争,因此 ReentrantLock 不能代表 CAS 在高并发下的表现。不过一般我们也不会直接使用 CAS,所以测试结果也凑合着看了。

测试使用的是 JMH 框架,号称能测到毫秒级。运行的机器是 40 核的,因此至少能保证同时竞争的线程是 40 个(如果 CPU 核数不足,尽管线程数多,真正同时并发的量可能并不多)。JDK 1.8 下测试。

首先测试用 synchronizedReentrantLock 同步自增操作,测试代码如下:

@Benchmark
@Group("lock")
@GroupThreads(4)
public void lockedOp() {
    try {
        lock.lock();
        lockCounter ++;
    } finally {
        lock.unlock();
    }
}

@Benchmark
@Group("synchronized")
@GroupThreads(4)
public void synchronizedOp() {
    synchronized (this) {
        rawCounter ++;
    }
}

结果如下:

性能对比:ReentrantLock vs Synchronized

自增操作 CPU 时间太短,适当增加每个操作的时间,改为往 linkedList 插入一个数据。代码如下:

@Benchmark
@Group("lock")
@GroupThreads(2)
public void lockedOp() {
    try {
        lock.lock();
        lockQueue.add("event");
        if (lockQueue.size() >= CLEAR_COUNT) {
            lockQueue.clear();
        }
    } finally {
        lock.unlock();
    }
}

@Benchmark
@Group("synchronized")
@GroupThreads(2)
public void synchronizedOp() {
    synchronized (this) {
        rawQueue.add("event");
        if (rawQueue.size() >= CLEAR_COUNT) {
            rawQueue.clear();
        }
    }
}

结果如下:

性能对比:ReentrantLock vs Synchronized
  1. 可以看到 ReentrantLock 的性能还是要高于 Synchronized 的。
  2. 在 2 个线程时吞吐量达到最低,而 3 个线程反而提高了,推测是因为两个线程竞争时一定会发生线程调度,而多个线程(不公平)竞争时有一些线程是可以直接从当前线程手中接过锁的。
  3. 随着线程数的增加,吞吐量只有少量的下降。首先推测因为同步代码最多只有一个线程在执行,所以线程数虽然增多,吞吐量是不会增加多少的。其次是大部分线程变成等待后就不太会被唤醒,因此不太会参与后续的竞争。
  4. (linkedlist 测试中)持有锁的时间增加后,ReentrantLock 与 Synchronized 的吞吐量差距减小了,应该是能佐证 CAS 线程重试的开销在增长的。

这个测试让我对 ReentrantLock 有了更多的信心,不过一般开发时还是建议用 synchronized, 毕竟大佬们还在不断优化中(看到有文章说 JDK 9 中的 Lock 和 synchronized 已经基本持平了)。

如果有人知道怎么更好地对比 CAS 和锁的性能,欢迎留言~


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

创京东

创京东

李志刚 / 中信出版社 / 2015-5-1 / CNY 49.80

1998年,刘强东创业,在中关村经销光磁产品。2004年,因为非典,京东偶然之下转向线上销售。2014年,京东市值已超400亿美元,跻身全球前十大互联网公司之列。 这是一个听起来很传奇的创业故事,但只有当事人了解创业维艰。 刚转向电商时,传统企业前景光明,而电商看起来前途未卜,京东如何能毅然转型并坚持到底?资金匮乏的时候,京东靠什么说服投资人?在强大的对手面前,京东靠什么反超并一路领先......一起来看看 《创京东》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具