问题

(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的性能提升。

推荐阅读

  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解析


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

死磕 java同步系列之ReentrantLock VS synchronized——结果可能跟你想的不一样的更多相关文章

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

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

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

    问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...

  3. 死磕 java同步系列之CyclicBarrier源码解析——有图有真相

    问题 (1)CyclicBarrier是什么? (2)CyclicBarrier具有什么特性? (3)CyclicBarrier与CountDownLatch的对比? 简介 CyclicBarrier ...

  4. 死磕 java同步系列之Phaser源码解析

    问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...

  5. 死磕 java同步系列之zookeeper分布式锁

    问题 (1)zookeeper如何实现分布式锁? (2)zookeeper分布式锁有哪些优点? (3)zookeeper分布式锁有哪些缺点? 简介 zooKeeper是一个分布式的,开放源码的分布式应 ...

  6. 死磕 java同步系列之redis分布式锁进化史

    问题 (1)redis如何实现分布式锁? (2)redis分布式锁有哪些优点? (3)redis分布式锁有哪些缺点? (4)redis实现分布式锁有没有现成的轮子可以使用? 简介 Redis(全称:R ...

  7. 死磕 java同步系列之终结篇

    简介 同步系列到此就结束了,本篇文章对同步系列做一个总结. 脑图 下面是关于同步系列的一份脑图,列举了主要的知识点和问题点,看过本系列文章的同学可以根据脑图自行回顾所学的内容,也可以作为面试前的准备. ...

  8. 死磕 java同步系列之StampedLock源码解析

    问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWrite ...

  9. 死磕 java同步系列之AQS终篇(面试)

    问题 (1)AQS的定位? (2)AQS的重要组成部分? (3)AQS运用的设计模式? (4)AQS的总体流程? 简介 AQS的全称是AbstractQueuedSynchronizer,它的定位是为 ...

随机推荐

  1. Springcloud 微服务 高并发(实战1):第1版秒杀

    疯狂创客圈 Java 高并发[ 亿级流量聊天室实战]实战系列之15 [博客园总入口 ] 前言 前言 疯狂创客圈(笔者尼恩创建的高并发研习社群)Springcloud 高并发系列文章,将为大家介绍三个版 ...

  2. IT兄弟连 HTML5教程 HTML5表单 HTML表单中的get和post方法

    指引 表单在网页应用中十分重要,基本上任何一个网站都必须使用到表单元素,所以表单的美观和易于交互对于网站设计就变得十分重要.HTML5对目前Web表单进行了全面提升,使得我们使用表单更加智能.它在保持 ...

  3. windows环境下Jmeter5.2的安装使用

    一.安装配置JDK Jmeter5.2依赖JDK1.8+版本,JDK安装百度搜索JAVA下载JDK,地址:https://www.oracle.com/technetwork/java/javase/ ...

  4. 当面试官要你介绍一下MQ时,该怎么回答?

    一.为什么要使用MQ消息中间件? 一个用消息队列的人,不知道为啥用,有点尴尬.没有复习这点,很容易被问蒙,然后就开始胡扯了. 回答:这个问题,咱只答三个最主要的应用场景,不可否认还有其他的,但是只答三 ...

  5. Linux系统:centos7下搭建ZooKeeper3.4中间件,常用命令总结

    本文源码:GitHub·点这里 || GitEE·点这里 一.下载解压 1.Zookeeper简介 Zookeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供 ...

  6. C# winfrom调用摄像头扫描二维码(完整版)

    前段时间看到一篇博客,是这个功能的,参考了那篇博客写了这个功能玩一玩,没有做商业用途.发现他的代码给的有些描述不清晰的,我就自己整理一下发出来记录一下. 参考博客链接:https://www.cnbl ...

  7. BASE64Encoder cannot be resolved to a type类似问题的解决办法

    我们有时候会碰见在JDK或者其他jar包中提供了某方法或者类,接口,而开发工具仍然报红,无法使用该类或者方法,那是应为我们的编译器没有识别,或者没有支持某些方法或者类 这个时候需要我们手动的开启.具体 ...

  8. SAP MM 无料号采购申请单中'评估价格'之填写

    SAP MM 无料号采购申请单中'评估价格'之填写 1),SAP系统中,采购申请里的'评估价格'来源有二, a)如果是有物料号的采购,则该价格来自于物料主数据里里的成本价(移动平均价或者标准价),自动 ...

  9. 三大免费开源的php语言cms系统 用好它们让你一天建好一个网站

    php语言只所以在web开发领域占据半壁江山,是因为它有太多的生态,成熟的框架体系,广泛的开源cms系统.建设网站的时候,都想提升开发效率,效率就是成本,如果你用原生php语言开发一个项目,既要设计数 ...

  10. Java学习笔记(5)--- Number类和Math 类,String类的应用,Java数组入门

    1.Number 和 Math 类: 在实际开发过程中,我们经常会遇到需要使用对象,而不是内置数据类型(int,double,float这些)的情形. 这种由编译器特别支持的包装称为装箱,所以当内置数 ...