文档章节

死磕 java同步系列之ReentrantLock VS synchronized——结果可能跟你想的不一样

 彤哥读源码
发布于 06/11 08:14
字数 1534
阅读 1343
收藏 21

问题

(1)ReentrantLock有哪些优点?

(2)ReentrantLock有哪些缺点?

(3)ReentrantLock是否可以完全替代synchronized?

简介

synchronized是Java原生提供的用于在多线程环境中保证同步的关键字,底层是通过修改对象头中的MarkWord来实现的。

ReentrantLock是Java语言层面提供的用于在多线程环境中保证同步的类,底层是通过原子更新状态变量state来实现的。

既然有了synchronized的关键字来保证同步了,为什么还要实现一个ReentrantLock类呢?它们之间有什么异同呢?

ReentrantLock VS synchronized

直接上表格:(手机横屏查看更方便)

功能ReentrantLocksynchronized
可重入支持支持
非公平支持(默认)支持
加锁/解锁方式需要手动加锁、解锁,一般使用try..finally..保证锁能够释放手动加锁,无需刻意解锁
按key锁不支持,比如按用户id加锁支持,synchronized加锁时需要传入一个对象
公平锁支持,new ReentrantLock(true)不支持
中断支持,lockInterruptibly()不支持
尝试加锁支持,tryLock()不支持
超时锁支持,tryLock(timeout, unit)不支持
获取当前线程获取锁的次数支持,getHoldCount()不支持
获取等待的线程支持,getWaitingThreads()不支持
检测是否被当前线程占有支持,isHeldByCurrentThread()不支持
检测是否被任意线程占有支持,isLocked()不支持
条件锁可支持多个条件,condition.await(),condition.signal(),condition.signalAll()只支持一个,obj.wait(),obj.notify(),obj.notifyAll()

对比测试

在测试之前,我们先预想一下结果,随着线程数的不断增加,ReentrantLock(fair)、ReentrantLock(unfair)、synchronized三者的效率怎样呢?

我猜测应该是ReentrantLock(unfair)> synchronized > ReentrantLock(fair)。

到底是不是这样呢?

直接上测试代码:(为了全面对比,彤哥这里把AtomicInteger和LongAdder也拿来一起对比了)

public class ReentrantLockVsSynchronizedTest {
    public static AtomicInteger a = new AtomicInteger(0);
    public static LongAdder b = new LongAdder();
    public static int c = 0;
    public static int d = 0;
    public static int e = 0;

    public static final ReentrantLock fairLock = new ReentrantLock(true);
    public static final ReentrantLock unfairLock = new ReentrantLock();


    public static void main(String[] args) throws InterruptedException {
        System.out.println("-------------------------------------");
        testAll(1, 100000);
        System.out.println("-------------------------------------");
        testAll(2, 100000);
        System.out.println("-------------------------------------");
        testAll(4, 100000);
        System.out.println("-------------------------------------");
        testAll(6, 100000);
        System.out.println("-------------------------------------");
        testAll(8, 100000);
        System.out.println("-------------------------------------");
        testAll(10, 100000);
        System.out.println("-------------------------------------");
        testAll(50, 100000);
        System.out.println("-------------------------------------");
        testAll(100, 100000);
        System.out.println("-------------------------------------");
        testAll(200, 100000);
        System.out.println("-------------------------------------");
        testAll(500, 100000);
        System.out.println("-------------------------------------");
//        testAll(1000, 1000000);
        System.out.println("-------------------------------------");
        testAll(500, 10000);
        System.out.println("-------------------------------------");
        testAll(500, 1000);
        System.out.println("-------------------------------------");
        testAll(500, 100);
        System.out.println("-------------------------------------");
        testAll(500, 10);
        System.out.println("-------------------------------------");
        testAll(500, 1);
        System.out.println("-------------------------------------");
    }

    public static void testAll(int threadCount, int loopCount) throws InterruptedException {
        testAtomicInteger(threadCount, loopCount);
        testLongAdder(threadCount, loopCount);
        testSynchronized(threadCount, loopCount);
        testReentrantLockUnfair(threadCount, loopCount);
//        testReentrantLockFair(threadCount, loopCount);
    }

    public static void testAtomicInteger(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < loopCount; j++) {
                    a.incrementAndGet();
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testAtomicInteger: result=" + a.get() + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testLongAdder(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < loopCount; j++) {
                    b.increment();
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testLongAdder: result=" + b.sum() + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testReentrantLockFair(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < loopCount; j++) {
                    fairLock.lock();
                    // 消除try的性能影响
//                    try {
                        c++;
//                    } finally {
                        fairLock.unlock();
//                    }
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testReentrantLockFair: result=" + c + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testReentrantLockUnfair(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < loopCount; j++) {
                    unfairLock.lock();
                    // 消除try的性能影响
//                    try {
                        d++;
//                    } finally {
                        unfairLock.unlock();
//                    }
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testReentrantLockUnfair: result=" + d + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

    public static void testSynchronized(int threadCount, int loopCount) throws InterruptedException {
        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            new Thread(() -> {
                for (int j = 0; j < loopCount; j++) {
                    synchronized (ReentrantLockVsSynchronizedTest.class) {
                        e++;
                    }
                }
                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("testSynchronized: result=" + e + ", threadCount=" + threadCount + ", loopCount=" + loopCount + ", elapse=" + (System.currentTimeMillis() - start));
    }

}

运行这段代码,你会发现结果大大出乎意料,真的是不测不知道,一测吓一跳,运行后发现以下规律:

随着线程数的不断增加,synchronized的效率竟然比ReentrantLock非公平模式要高!

彤哥的电脑上大概是高3倍左右,我的运行环境是4核8G,java版本是8,请大家一定要在自己电脑上运行一下,并且最好能给我反馈一下。

彤哥又使用Java7及以下的版本运行了,发现在Java7及以下版本中synchronized的效率确实比ReentrantLock的效率低一些。

总结

(1)synchronized是Java原生关键字锁;

(2)ReentrantLock是Java语言层面提供的锁;

(3)ReentrantLock的功能非常丰富,解决了很多synchronized的局限性;

(4)至于在非公平模式下,ReentrantLock与synchronized的效率孰高孰低,彤哥给出的结论是随着Java版本的不断升级,synchronized的效率只会越来越高;

彩蛋

既然ReentrantLock的功能更丰富,而且效率也不低,我们是不是可以放弃使用synchronized了呢?

答:我认为不是。因为synchronized是Java原生支持的,随着Java版本的不断升级,Java团队也是在不断优化synchronized,所以我认为在功能相同的前提下,最好还是使用原生的synchronized关键字来加锁,这样我们就能获得Java版本升级带来的免费的性能提升的空间。

另外,在Java8的ConcurrentHashMap中已经把ReentrantLock换成了synchronized来分段加锁了,这也是Java版本不断升级带来的免费的synchronized的性能提升。

推荐阅读

  1. 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁

  2. 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁

  3. 死磕 java同步系列之AQS起篇

  4. 死磕 java同步系列之自己动手写一个锁Lock

  5. 死磕 java魔法类之Unsafe解析

  6. 死磕 java同步系列之JMM(Java Memory Model)

  7. 死磕 java同步系列之volatile解析

  8. 死磕 java同步系列之synchronized解析


欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

qrcode

© 著作权归作者所有

彤哥读源码

粉丝 189
博文 52
码字总数 128643
作品 0
深圳
私信 提问
加载中

评论(6)

彤哥读源码

引用来自“freezingsky”的评论

所以, 在开发时, 尽可能优先使用synchronized.除了使用的便捷性之外 , 更是因为JVM本身对其会做更多的优化. 除非 ,当synchronized使用无法达到期望值时, 才考虑使用其它的无锁模型.

引用来自“彤哥读源码”的评论

在开发时,也很少会用到synchronized吧,现在都是分布式了,要用分布式锁啦😄

引用来自“freezingsky”的评论

一说分布式锁, 那又是一个很大篇幅的事.期待你补充啊!
会写的💪
f
freezingsky

引用来自“freezingsky”的评论

所以, 在开发时, 尽可能优先使用synchronized.除了使用的便捷性之外 , 更是因为JVM本身对其会做更多的优化. 除非 ,当synchronized使用无法达到期望值时, 才考虑使用其它的无锁模型.

引用来自“彤哥读源码”的评论

在开发时,也很少会用到synchronized吧,现在都是分布式了,要用分布式锁啦😄
一说分布式锁, 那又是一个很大篇幅的事.期待你补充啊!
彤哥读源码

引用来自“freezingsky”的评论

所以, 在开发时, 尽可能优先使用synchronized.除了使用的便捷性之外 , 更是因为JVM本身对其会做更多的优化. 除非 ,当synchronized使用无法达到期望值时, 才考虑使用其它的无锁模型.
在开发时,也很少会用到synchronized吧,现在都是分布式了,要用分布式锁啦😄
f
freezingsky
所以, 在开发时, 尽可能优先使用synchronized.除了使用的便捷性之外 , 更是因为JVM本身对其会做更多的优化. 除非 ,当synchronized使用无法达到期望值时, 才考虑使用其它的无锁模型.
彤哥读源码

引用来自“zhonghao11”的评论

synchronized在java8以后性能确实提高了很多。
是的,java8中ConcurrentHashMap都改用synchronized来加锁了。
z
zhonghao11
synchronized在java8以后性能确实提高了很多。
【死磕Java并发】—– 死磕 Java 并发精品合集

【死磕 Java 并发】系列是 LZ 在 2017 年写的第一个死磕系列,一直没有做一个合集,这篇博客则是将整个系列做一个概览。 先来一个总览图: 【高清图,请关注“Java技术驿站”公众号,回复:脑...

chenssy
2018/07/22
0
0
死磕 java同步系列之CountDownLatch源码解析

问题 (1)CountDownLatch是什么? (2)CountDownLatch具有哪些特性? (3)CountDownLatch通常运用在什么场景中? (4)CountDownLatch的初始次数是否可以调整? 简介 CountDownLatch,可以...

彤哥读源码
前天
0
0
死磕 java同步系列之ReentrantReadWriteLock源码解析

问题 (1)读写锁是什么? (2)读写锁具有哪些特性? (3)ReentrantReadWriteLock是怎么实现读写锁的? (4)如何使用ReentrantReadWriteLock实现高效安全的TreeMap? 简介 读写锁是一种特...

彤哥读源码
06/13
0
1
死磕 java同步系列之Semaphore源码解析

问题 (1)Semaphore是什么? (2)Semaphore具有哪些特性? (3)Semaphore通常使用在什么场景中? (4)Semaphore的许可次数是否可以动态增减? (5)Semaphore如何实现限流? 简介 Semaph...

彤哥读源码
前天
0
0
死磕 java同步系列之ReentrantLock源码解析(二)——条件锁

问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等...

彤哥读源码
06/03
0
0

没有更多内容

加载失败,请刷新页面

加载更多

局域网能互相ping通,ubuntu虚拟机不能上外网

【问题】 桥接模式老是无法上网,查看本机IP发现被分配了一个私网地址,猜测应该是虚拟DHCP服务器没有打开,于是查看Ubuntu的网络配置: /etc/network/interfaces 发现没有dhcp配置的信息,只...

tahiti_aa
22分钟前
0
0
以太坊助记词PHP开发包简介

以太坊助记词PHP开发包用来为PHP以太坊应用增加助记词和层级确定密钥支持能力。下载地址:以太坊助记词php开发包 。 1、开发包概述 以太坊助记词PHP开发包主要包括以下特性: 生成符合BIP39...

汇智网教程
昨天
2
0
系统监控-分布式调用链Skywalking

1. 为什么要使用分布式调用链技术? 随着公司业务的高速发展,公司服务之间的调用关系愈加复杂,如何理清并跟踪它们之间的调用关系就显的比较关键。线上每一个请求会经过多个业务系统,并产生...

秋日芒草
昨天
3
0
告诉自己的一些建议

摆脱学生心态 尽快发挥自己价值,让公司感知自己的存在,才是王道 选择比努力重要 自己附着的平台的经济体要是一个快速崛起的行业 转行趁早,年龄越大选择成本越高 趁早大量试错,学习新领域...

林怡丰
昨天
3
0
Windows下安装Redis

下载地址: 3.0老版已不维护更新:https://github.com/MicrosoftArchive/redis/releases 4.0版 https://github.com/tporadowski/redis/releases 中文官网:http://www.redis.net.cn/ https:......

Aeroever
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部