死磕 java同步系列之ReentrantLock VS synchronized——结果可能跟你想的不一样
问题
(1)ReentrantLock有哪些优点?
(2)ReentrantLock有哪些缺点?
(3)ReentrantLock是否可以完全替代synchronized?
简介
synchronized是Java原生提供的用于在多线程环境中保证同步的关键字,底层是通过修改对象头中的MarkWord来实现的。
ReentrantLock是Java语言层面提供的用于在多线程环境中保证同步的类,底层是通过原子更新状态变量state来实现的。
既然有了synchronized的关键字来保证同步了,为什么还要实现一个ReentrantLock类呢?它们之间有什么异同呢?
ReentrantLock VS synchronized
直接上表格:(手机横屏查看更方便)
| 功能 | ReentrantLock | synchronized |
|---|---|---|
| 可重入 | 支持 | 支持 |
| 非公平 | 支持(默认) | 支持 |
| 加锁/解锁方式 | 需要手动加锁、解锁,一般使用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的性能提升。
推荐阅读
欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

死磕 java同步系列之ReentrantLock VS synchronized——结果可能跟你想的不一样的更多相关文章
- 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁
问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等 ...
- 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁
问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...
- 死磕 java同步系列之CyclicBarrier源码解析——有图有真相
问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...
- 死磕 java同步系列之Phaser源码解析
问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...
- 死磕 java同步系列之zookeeper分布式锁
问题 (1)zookeeper如何实现分布式锁? (2)zookeeper分布式锁有哪些优点? (3)zookeeper分布式锁有哪些缺点? 简介 zooKeeper是一个分布式的,开放源码的分布式应 ...
- 死磕 java同步系列之redis分布式锁进化史
问题 (1)redis如何实现分布式锁? (2)redis分布式锁有哪些优点? (3)redis分布式锁有哪些缺点? (4)redis实现分布式锁有没有现成的轮子可以使用? 简介 Redis(全称:R ...
- 死磕 java同步系列之终结篇
简介 同步系列到此就结束了,本篇文章对同步系列做一个总结. 脑图 下面是关于同步系列的一份脑图,列举了主要的知识点和问题点,看过本系列文章的同学可以根据脑图自行回顾所学的内容,也可以作为面试前的准备. ...
- 死磕 java同步系列之StampedLock源码解析
问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...
- 死磕 java同步系列之AQS终篇(面试)
问题 (1)AQS的定位? (2)AQS的重要组成部分? (3)AQS运用的设计模式? (4)AQS的总体流程? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为 ...
随机推荐
- 【使用篇二】SpringBoot整合SpringDataJPA(18)
一.pom.xml添加依赖 <dependencies> <!--web--> <dependency> <groupId>org.springfram ...
- element form 对单个字段做验证
this.$refs[formName].validateField('phone', phoneError => { //验证手机号码是否正确 if (!phoneError) { conso ...
- <String> 161 358
161. One Edit Distance 1. 两个字符串的长度之差大于1,直接返回False. 2. 两个字符串的长度之差等于1,长的那个字符串去掉一个字符,剩下的应该和短的字符串相同. 3. ...
- 【安富莱】V6,V5开发板用户手册,重在BSP驱动包设计方法,HAL库的框架学习,授人以渔(2019-11-04)
说明: 1.本教程重在BSP驱动包设计方法和HAL库的框架学习,并将HAL库里面的各种弯弯绕捋顺,从而方便我们的程序设计. 2.本次工程延续以往的代码风格,从底层BSP驱动包到应用代码,变量命名,文件 ...
- 如何封装$on,$emit,$off——学vue前你必须懂得封装!
let evevtListenr = {} 封装$on const $on = (eventName,cb)=>{ if(!evevtListenr[eventName]){ ...
- 什么是面向对象编程(OOP)?
Java 程序员第一个要了解的基础概念就是:什么是面向对象编程(OOP)? 玩过 DOTA2 (一款推塔杀人的游戏)吗?里面有个齐天大圣的角色,欧洲战队玩的很溜,国内战队却不怎么会玩,自家人不会玩自家 ...
- NetCore的Docker部署
NetCore的Docker部署 一.NetCore与Docker Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux或 ...
- 爬虫爬取m3u8视频文件
一.m3u8视频格式 一般m3u8文件和 视频流ts文件放在同一目录 而m3u8文件格式存放的一般都是ts 文件的一个列表 二.根据m3u8视频存放以及写法的规律 思路 我们一般网站上能找到的m3u8 ...
- vscode解决nuget插件不能使用的问题
错误提示 使用vscode安装nuget插件之后出现错误: "Versioning information could not be retrieved from the NuGet pac ...
- fsockopen反弹shell脚本
<?php error_reporting (E_ERROR); ignore_user_abort(true); ini_set('max_execution_time',0); $os = ...