阿粉写了八千多字,就是为了把 ReentrantLock 讲透(一)

简介: 啥是可重入锁呢?比如:线程 1 通过调用 lock() 方法获取锁之后,再调用 lock 时,就不会再进行阻塞获取锁,而是直接增加重试次数。还记得 synchronized 吗?它有 monitorenter 和 monitorexit 两种指令来保证锁,而它们的作用可以理解为每个锁对象拥有一个锁计数器,也就是如果再次调用 lock() 方法,计数器会进行加 1 操作

ReentrantLock 是可重入锁

啥是可重入锁呢?比如:线程 1 通过调用 lock() 方法获取锁之后,再调用 lock 时,就不会再进行阻塞获取锁,而是直接增加重试次数。

还记得 synchronized 吗?它有 monitorenter 和 monitorexit 两种指令来保证锁,而它们的作用可以理解为每个锁对象拥有一个锁计数器,也就是如果再次调用 lock() 方法,计数器会进行加 1 操作

所以, synchronized 和 ReentrantLock 都是可重入锁

17.jpg

ReentrantLock 与 synchronized 区别

既然 synchronized 和 ReentrantLock 都是可重入锁,那 ReentrantLock 与 synchronized 有什么区别呢?

synchronized 是 Java 语言层面提供的语法,所以不需要考虑异常;ReentrantLock 是 Java 代码实现的锁,所以必须先要获取锁,然后再正确释放锁

synchronized 在获取锁时必须一直等待没有额外的尝试机制;ReentrantLock 可以尝试获取锁(这一点等下分析源码时会看到)

ReentrantLock 支持获取锁时的公平和非公平选择

不 BB 了,直接上源码

18.jpg

lock & NonfairSync & FairSync 详解

lock

锁的入口是 lock() 方法:

public void lock() {
    sync.lock();
}

其中, sync 是 ReentrantLock 的静态内部类,它继承 AQS 来实现重入锁的逻辑, Sync 有两个具体实现类: NonfairSync 和 FairSync

NonfairSync

先来看一下 NonfairSync :

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    /**
    * Performs lock.  Try immediate barge, backing up to normal
    * acquire on failure.
    */
    // 重写 Sync 的 lock 方法
    final void lock() {
     // 先不管其他,上来就先 CAS 操作,尝试抢占一下锁
        if (compareAndSetState(0, 1))
         // 如果抢占成功,就获得了锁
            setExclusiveOwnerThread(Thread.currentThread());
        else
         // 没有抢占成功,调用 acquire() 方法,走里面的逻辑
            acquire(1);
    }
 // 重写了 AQS 的 tryAcquire 方法
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

FairSync

接下来看一下 FairSync :

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
 // 重写 Sync 的 lock 方法
    final void lock() {
        acquire(1);
    }
    /**
    * Fair version of tryAcquire.  Don't grant access unless
    * recursive call or no waiters or is first.
    */
    // 重写了 Sync 的 tryAcquire 方法
    protected final boolean tryAcquire(int acquires) {
     // 获取当前执行的线程
        final Thread current = Thread.currentThread();
        // 获取 state 的值
        int c = getState();
        // 在无锁状态下
        if (c == 0) {
         // 没有前驱节点且替换 state 的值成功时
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                // 保存当前获得锁的线程,下次再来时,就不需要尝试竞争锁,直接重入即可
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
         // 如果是同一个线程来获得锁,直接增加重入次数即可
            int nextc = c + acquires;
            // nextc 小于 0 ,抛异常
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            // 获取锁成功
            return true;
        }
        // 获取锁失败
        return false;
    }
}

总结 NonfairSync 与 FairSync

到这里,应该就比较清楚了, Sync 有两个具体的实现类,分别是:

  • NonfairSync :可以抢占锁,调用 NonfairSync 时,不管当前队列上有没有其他线程在等待,上来我就先 CAS 操作一番,成功了就获得了锁,没有成功就走 acquire 的逻辑;在释放锁资源时,走的是 Sync.nonfairTryAcquire 方法
  • FairSync :所有线程按照 FIFO 来获取锁,在 lock 方法中,没有 CAS 尝试,直接就是 acquire 的逻辑;在释放资源时,走的是自己的 tryAcquire 逻辑

接下来咱们看看 NonfairSync 和 FairSync 是如何获取锁的

相关文章
|
5月前
|
存储 安全 Java
《吊打面试官》从根上剖析ReentrantLock的来龙去脉
《吊打面试官》从根上剖析ReentrantLock的来龙去脉
|
5月前
|
Java
面试官:说一说CyclicBarrier的妙用!我:这个没用过...
【5月更文挑战第5天】面试官:说一说CyclicBarrier的妙用!我:这个没用过...
36 2
|
5月前
|
存储 缓存 Oracle
Java线程池,白话文vs八股文,原来是这么回事!
一、线程池原理 1、白话文篇 1.1、正式员工(corePoolSize) 正式员工:这些是公司最稳定和最可靠的长期员工,他们一直在工作,不会被解雇或者辞职。他们负责处理公司的核心业务,比如生产、销售、财务等。在Java线程池中,正式员工对应于核心线程(corePoolSize),这些线程会一直存在于线程池中。他们负责执行线程池中的任务,如果没有任务,他们会等待新的任务到来。 1.2、所有员工(maximumPoolSize) 所有员工:这些是公司所有的员工,包括正式员工和外包员工。他们共同组成了公司的团队,协作完成公司的各种业务。在Java线程池中,所有员工对应于所有线程(maxim
1.5w字,30图带你彻底掌握 AQS!(建议收藏)
AQS( AbstractQueuedSynchronizer )是一个用来构建锁和同步器(所谓同步,是指线程之间的通信、协作)的框架,Lock 包中的各种锁(如常见的 ReentrantLock, ReadWriteLock), concurrent它包中的各种同步器(如 CountDownLatch, Semaphore, CyclicBarrier)都是基于 AQS 来构建,所以理解 AQS 的实现原理至关重要,AQS 也是面试中区分候选人的常见考点,我们务必要掌握,本文将用循序渐进地介绍 AQS,相信大家看完一定有收获。文章目录如下
|
存储 消息中间件 缓存
四万字爆肝总结java多线程所有知识点(史上最全总结)
全文从多线程的实现方式、线程的状态、线程的方法、线程的同步、线程的通讯、等角度对多线程的基础知识进行总结
461 1
四万字爆肝总结java多线程所有知识点(史上最全总结)
|
存储 安全 Java
Java多线程基础——两万字详解
进程简单来说就是正在运行的程序,是可以通过双击执行的.exe文件,打开我们电脑的任务管理器,可以看到我们的电脑正在执行的进程,目前我们的电脑都是多进程模式。
119 0
Java多线程基础——两万字详解
|
Java 调度 Android开发
七千字带你深入JUC线程基础
七千字带你深入JUC线程基础
133 0
七千字带你深入JUC线程基础
|
Java
这篇 ReentrantLock 看不懂,加我我给你发红包(三)
在开始本篇文章的内容讲述前,先来回答我一个问题,为什么 JDK 提供一个 synchronized 关键字之后还要提供一个 Lock 锁,这不是多此一举吗?难道 JDK 设计人员都是沙雕吗?
131 1
这篇 ReentrantLock 看不懂,加我我给你发红包(三)
|
Java 调度
这篇 ReentrantLock 看不懂,加我我给你发红包(二)
在开始本篇文章的内容讲述前,先来回答我一个问题,为什么 JDK 提供一个 synchronized 关键字之后还要提供一个 Lock 锁,这不是多此一举吗?难道 JDK 设计人员都是沙雕吗?
95 0
这篇 ReentrantLock 看不懂,加我我给你发红包(二)
|
Java 调度
这篇 ReentrantLock 看不懂,加我我给你发红包(一)
在开始本篇文章的内容讲述前,先来回答我一个问题,为什么 JDK 提供一个 synchronized 关键字之后还要提供一个 Lock 锁,这不是多此一举吗?难道 JDK 设计人员都是沙雕吗?
82 0
这篇 ReentrantLock 看不懂,加我我给你发红包(一)