性能对比:ReentrantLock vs Synchronized

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

内容简介:最近遇到高并发引起的性能问题,最终定位到的问题是压测在一个 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 和锁的性能,欢迎留言~


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

查看所有标签

猜你喜欢:

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

jQuery 技术内幕

jQuery 技术内幕

高云 / 机械工业出版社 / 2014-1-1 / 99元

本书首先通过“总体架构”梳理了各个模块的分类、功能和依赖关系,让大家对jQuery的工作原理有大致的印象;进而通过“构造 jQuery 对象”章节分析了构造函数 jQuery() 的各种用法和内部构造过程;接着详细分析了底层支持模块的源码实现,包括:选择器 Sizzle、异步队列 Deferred、数据缓存 Data、队列 Queue、浏览器功能测试 Support;最后详细分析了功能模块的源码实......一起来看看 《jQuery 技术内幕》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具