死磕 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,它的定位是为 ...
随机推荐
- ReactNative: 使用Image图片组件
一.简介 在应用程序中,图片组件非常常见,不论是缩略图.大图.还是小图标等等,都需要使用图片组件进行显示.在Web开发中提供了<img/>标签显示图片,在iOS中提供了UIImageVie ...
- nginx如何实现负载均衡以及实现方式
什么是ngnix? Nginx是一个http服务器.是一个使用c语言开发的高性能的http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器.nginx能够支撑5万并发链接,并且cpu ...
- 2019年全国高校计算机能力挑战赛初赛C语言解答
http://www.ncccu.org.cn 2019年全国高校计算机能力挑战赛分设大数据算法赛,人工智能算法赛,Office高级应用赛,程序设计赛4大赛项 C语言初赛解答 1:编程1 16.现有一 ...
- vmalloc/vfree问题思考记录
arm 32 用户进程陷入内核态通过vmalloc/vfree分配内存的流程 内核在更新非连续内存区对应的页表项是非常懒惰的.--<深入理解linux内核> arm 32 只有一个PGD ...
- 我的计划任务 --- 实现市电停电安全关闭群辉,Windows, Linux等设备
有一次突然停电,我的群辉DS218+ 的一块硬盘出现故障了,让我担心我的数据安全,其实我是有UPS, 不是在线式的,然后就想如何实现停电自动关机呢? 经过半天的了解,其实群辉支持telnet协议,于是 ...
- Linux下使命令不受终端断开的影响,保持在后台运行的几种方法及原理
摘自https://www.ibm.com/developerworks/cn/linux/l-cn-nohup/ 记录一下Linux下使命令不受终端断开的影响,保持在后台运行的几个方法及其原理.当用 ...
- GO-逻辑判断(if,else if,else,switch)
一.if逻辑判断 package main import "fmt" func main() { var a =10; if a>10 { //大括号前不能回车 fmt.Pr ...
- Xcode报错:could not attach to pid:"1764"
这种错误不是什么问题,按照参考链接操作即可,亲测有效: https://www.cnblogs.com/luorende/p/6295945.html 在运行项目时出现了如下错误 (基本上重新启动项目 ...
- JS&Jquery基础之窗口对象的关系总结
1.top 该变更永远指分割窗口最高层次的浏览器窗口.如果计划从分割窗口的最高层次开始执行命令,就可以用top变量.2.opener opener用于在window.open的页面引用执行该wi ...
- linux下安装oracle数据库--干货
1.修改系统名称,关闭防火墙,selinux.2.挂载镜像,并写入开机自动挂载.挂载点为/mnt/yummount -t iso9660 -o,loop /soft/Centos6.iso /mnt/ ...