死磕 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的性能提升。
推荐阅读
欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Spring Boot 配置文件中的花样,看这一篇足矣!
在快速入门一节中,我们轻松的实现了一个简单的RESTful API应用,体验了一下Spring Boot给我们带来的诸多优点,我们用非常少的代码量就成功的实现了一个Web应用,这是传统的Spring应用无法办到的,虽然我们在实现Controller时用到的代码是一样的,但是在配置方面,相信大家也注意到了,在上面的例子中,除了Maven的配置之后,就没有引入任何的配置。 这就是之前我们所提到的,Spring Boot针对我们常用的开发场景提供了一系列自动化配置来减少原本复杂而又几乎很少改动的模板化配置内容。但是,我们还是需要去了解如何在Spring Boot中修改这些自动化的配置内容,以应对一些特殊的场景需求,比如:我们在同一台主机上需要启动多个基于Spring Boot的web应用,若我们不为每个应用指定特别的端口号,那么默认的8080端口必将导致冲突。 如果您还有在读我的Spring Cloud系列教程,其实有大量的工作都会是针对配置文件的。所以我们有必要深入的了解一些关于Spring Boot中的配置文件的知识,比如:它的配置方式、如何实现多环境配置,配置信息的加载顺序等。 配置基...
- 下一篇
cors跨域之简单请求与预检请求(发送自定义请求头)
引子 前后端分离这个问题,对cors的应用不断增多,暴露出的问题也接踵而至。 正所谓虑一千次,不如去做一次。 犹豫一万次,不如实践一次,本篇主要讨论在发送ajax请求,头部带上自定义token验证验证,暴露出的跨域问题。 先说说定义 CORS:跨来源资源共享(CORS)是一份浏览器技术的规范,提供了 Web 服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,是 JSONP 模式的现代版。与 JSONP 不同,CORS 除了 GET 要求方法以外也支持其他的 HTTP 要求。用 CORS 可以让网页设计师用一般的 XMLHttpRequest,这种方式的错误处理比JSONP要来的好,JSONP对于 RESTful 的 API 来说,发送 POST/PUT/DELET 请求将成为问题,不利于接口的统一。但另一方面,JSONP 可以在不支持 CORS 的老旧浏览器上运作。不过现代的浏览器(IE10以上)基本都支持 CORS。 预检请求(option):在 CORS 中,可以使用 OPTIONS 方法发起一个预检请求(一般都是浏览检测到请求跨域时,会自动发起),以检测实际请求是否可以被...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Mario游戏-低调大师作品
- CentOS8安装Docker,最新的服务器搭配容器使用
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8编译安装MySQL8.0.19
- SpringBoot2配置默认Tomcat设置,开启更多高级功能