【认真学java—锁】可重入锁ReentrantLock

码影弦歌者
• 阅读 1496

学习记录,有不对的或更好见解,还请多多交流

参考

需了解的知识

synchronized
不明白,可以看以下文章,慢慢看,沉下心

看完以上文章,就不用看本文了😀

ReenentrantLock概述

ReentrantLock 是对synchronized的扩展。突出的特点就是可重入,更灵活。可重入是指什么?那反过来问不可重入是什么?
不可重入顾名思义就是不可以重复进入,具体指的是在没有释放锁的情况下,不可以再获取锁,否则就会造成死锁dead lock。(很多名词都是英文翻译过来的,所以英文很重要,不然获取的知识就是二手的)
错误例子如下:

public void test(Object object){
    synchronized(object){           // first lock
        synchronized(object){       // second lock
            //do something
        }
    }
}

上面的second lock 永远也获取不到锁,所以造成了死锁,程序就卡死在第二次加锁。

主要原理:ReentrantLock类的内部有个Sync类继承了AbstractQueuedSynchronizer(AQS封装了多线程下队列的同步操作)对锁操作进行了封装。NonfairSync和FairSync继承了Sync类,并重载了各自的tryAcquire方法。通过不同的构造器ReentrantLock(),ReentrantLock(boolean fair)来实例化不同的Sync对象,以创建公平可重入锁 / 不公平可重入锁对象,后续的所有操作都是基于这个lock对象进行操作。

而对于ReentrantLock来说,它提供了在锁没释放时,可以再次获取锁的方法 tryLock。先来看看ReentrantLock中重要的方法及含义:

  1. lock 和synchronized类似,仅当没有其他线程持有该对象的锁时,才能加锁成功。在其他线程持有锁期间,该线程会进去睡眠状态,(dormant),直到获取到锁。区别在于:若自己对自己再次加锁,是可以获得锁的。(可重入)(注:synchronized对自己加锁两次是死锁)
  2. 构造器中的参数fairness,设为true时,锁会优先让等待最长时间的线程持有;若为false(默认为false),则不保证线程持有锁的先后顺序。
    注意:fairness为true,会严重影响整体性能。而且fairness并不保证线程的调度的公平(不保证每个线程平均获取锁,只是让排队最长时间先的获取而已)
  3. tryLock方法不受fairness设置影响。即使有其他线程获取了锁,锁还是可以被继续获取的,最大支持递归 2147483647次(int的最大整数)。(可重入

注tryLock()在没获得锁时,不会像lock()一样进入睡眠等待;tryLock(long timeout, TimeUnit unit)会睡眠等待timeout时长,同时会fairness规则

  1. unlock释放自己获得的锁,使锁的次数-1。

源码:
1、Sync内部类nonfairTryAcquire方法

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {      //初始态,没有线程持有锁时
        if (compareAndSetState(0, acquires)) {  //见下文2
            setExclusiveOwnerThread(current);   //锁住,禁止其他线程访问
            return true; 
        }
    }
    else if (current == getExclusiveOwnerThread()) {    //对自己线程再次加锁
        int nextc = c + acquires;
        if (nextc < 0) // overflow
        throw new Error("Maximum lock count exceeded");
        setState(nextc);        //增加锁的次数
        return true; 
    }
    return false;
}

2、AQS中的compareAndSetState(int expect, int update)方法

protected final boolean compareAndSetState(int expect, int update) {
    return STATE.compareAndSet(this, expect, update);   //见下文3
}

3、本地方法(C++编写的)compareAndSet位于java.lang.invoke.VarHandle下(常说的CAS)
官网api链接:CAS

如果witness value == exceptedValue,则赋新值newVaule。其中witness value 为main memory中该变量的值(getVolatile获取);赋新值通过setVolatile赋值。
return true,赋值成功。
return false,变量被其他线程改变了,赋值失败。

4、NonfairSync类的tryAcquire就是调用Sync中nonfairTryAcquire(acquires);
5、FairSync中的tryAcquire

/**
 * Fair version of tryAcquire.  Don't grant access unless 
 * recursive call or no waiters or is first. 
 * 翻译:公平锁,只有在重复获取锁 / 没有其他等得更久的线程在等待获取锁 / 第一个获取锁时,
 * 才能获取到锁。
 */
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
     int c = getState();
     if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true; 
            }
     }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true; 
        }
    return false;
}

6、hasQueuedPredecessors(AQS中)

/*
* Queries whether any threads have been waiting to acquire longer than the current thread.
* 翻译:查看是否有其他想获取锁的线程比当前线程等的长(公平锁的原则:先服务等得久的)
*/
源码略,因为需要先看懂AQS这个类的实现机制
点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
java多线程之ReentrantLock
前言相信学过java的人都知道synchronized这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么实现的。如果真是这样,而且你有兴趣了解,今天我将带领你轻松的学习下java中非常重要,也非常基础的可重入锁ReentrantLock的实现机制。R
Wesley13 Wesley13
3年前
java中的锁
记录一下公平锁,非公平锁,可重入锁(递归锁),读写锁,自旋锁的概念,以及一些和锁有关的java类。公平锁与非公平锁:公平锁就是在多线程环境下,每个线程在获取锁时,先查看这个锁维护的队列,如果队列为空或者自身就是等待队列的第一个,就占有锁。否则就加入到等待队列中,按照FIFO的顺序依次占有锁。非公平锁会一上来就试图占
Wesley13 Wesley13
3年前
java 面试知识点笔记(十二)多线程与并发
问:synchronized和ReentrantLock的区别?ReentrantLock(可重入锁)位于java.util.concurrent.locks包(著名的juc包是由Douglea大神写的AQS抽象类框架衍生出来的应用)和CountDownLatch、FutureTask、Semaphore一样基于AQS实现
Wesley13 Wesley13
3年前
java并发相关(四)——关于synchronized的可重入性,线程切换实现原理与是否公平锁
一、可重入性  关于synchronized的可重入性的证明,我们可以通过A类内写两个同步方法syncA(),syncB()。然后syncA内调用syncB,调用syncA发现代码可正常执行,来证明这一点。  当处于无锁阶段时,划掉,都重入了不可能处于无锁。  当处于偏向锁阶段时,由之前对偏向锁的解释可知,偏向当前线程id是,当前线程可直
Stella981 Stella981
3年前
ReenTrantLock可重入锁和synchronized的区别
ReenTrantLock可重入锁和synchronized的区别可重入性:从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入的,两者关于这个的区别不大。两者都是同一个线程没进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。锁的实现:S
Wesley13 Wesley13
3年前
Java并发源码之ReentrantLock
ReentrantLock介绍ReentrantLock是一个可重入的互斥锁,与使用synchronized方法和语句访问的隐式监视锁具有相同的基本行为和语义,但具有扩展功能。ReentrantLock属于最后一个成功加锁并且还没有释放锁的线程。当一个线程请求lock时,如果锁不属于任何线程,将立马得到这个锁;如果锁已经被
Stella981 Stella981
3年前
ReentrantReadWriteLock实现原理
  在java并发包java.util.concurrent中,除了重入锁ReentrantLock外,读写锁ReentrantReadWriteLock也很常用。在实际开发场景中,在使用共享资源时,可能读操作远远多于写操作。这种情况下,如果对这部分共享资源能够让多个线程读的时候不受阻塞,仅仅在写的时候保证安全性,这样效率会得到显著提升。读写锁Reentra
Wesley13 Wesley13
3年前
Mysql 乐观锁 和悲观锁
平时看博客或技术文章的时候,经常被各种锁搞得晕晕乎乎,包括在自旋锁、可重入锁、公平锁等等、乐观锁、悲观锁、行锁、表锁、意向锁、排它锁等。前段时间终于把Java多线程相关的锁有机会学习了一遍。现在开始整理mysql相关的锁概念。先从乐观锁和悲观锁开始聊聊。首先要知道,乐观锁和悲观锁不是真实存在的锁,只是两种抽象概念性的东西,就相当于Java中的接口,只
Wesley13 Wesley13
3年前
Java中所有锁介绍
在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下:1.公平锁/非公平锁2.可重入锁/不可重入锁3.独享锁/共享锁4.互斥锁/读写锁5.乐观锁/悲观锁6.分段锁7.偏向锁/轻量级锁/重量级锁8.自旋锁上面是很多锁的名词,这些分类并不是全是指锁的
并发情况如何实现加锁来保证数据一致性? | 京东云技术团队
单体架构下锁的实现方案1\.ReentrantLock全局锁ReentrantLock(可重入锁),指的是一个线程再次对已持有的锁保护的临界资源时,重入请求将会成功。简单的与我们常用的Synchronized进行比较:||ReentrantLock|Syn
美凌格栋栋酱 美凌格栋栋酱
4个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(