问题
(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 的性能提升.
来源: https://www.cnblogs.com/tong-yuan/p/11001615.html