ReentrantLock源码分析

1、ReentrantLock介绍

ReentrantLock就是一个互斥锁。类比sync。套路都类似,只不过sync是基于对象头和类实现的,ReentrantLock基于AQS实现的。

static ReentrantLock lock = new ReentrantLock();
publicstaticvoidmain(String[] args){
    lock.lock();  //  sync(){//  ---  业务要求原子性
    lock.unlock();  //  }
}

2、AQS分析

AQS是JUC包下的一个基类,就是JUC包下提供的一个抽象类AbstractQueuedSynchronizer

AQS中有两个核心内容:

  • state属性:state基于volatile修饰,并且采用CAS的方式去修改,这样state就保证了原子性,可见性,有序性。 同一时间点,只有会有一个线程成功的修改state。修改过程就是将state从旧值修改为新值
  • 双向链表:线程在竞争资源时,可能会出现没有拿到资源,线程不能一直CAS,因为频繁的CAS会造成浪费CPU资源,线程需要挂起。挂起就需要考虑线程存在哪。 线程会存储在Node对象中,并且没有获取到资源的线程可能或有多个,多个Node就会组成一个双向链表。

ReentrantLock加锁流程(一)(重要)

3、ReentrantLock源码剖析-lock方法

// 非公平锁finalvoidlock(){
    // 直接CAS尝试将state从0改为1,如果成功,返回trueif (compareAndSetState(0, 1))
        // 获取锁资源成功,直接将exclusiveOwnerThread设置为当前线程,代表当前线程持有着锁资源
        setExclusiveOwnerThread(Thread.currentThread());
    else// 修改state失败,执行acquire方法
        acquire(1);
}

// 公平锁finalvoidlock(){
    // 执行acquire方法
    acquire(1);
}

4、ReentrantLock源码剖析-acquire方法

publicfinalvoidacquire(int arg){
    // tryAcquire(公平&非公平),再次尝试拿锁资源。 如果拿到返回true,没拿到返回falseif (!tryAcquire(arg) &&
        // 没拿到锁资源,执行addWaiter以及acquireQueued// addWaiter:拿锁失败,将当前线程封装为Node对象,并且添加到AQS双向链表的末尾// acquireQueued:acquireQueued中会根据情况尝试再次执行tryAcquire,如果多次尝试依然拿不到,挂起线程(WAITING)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
}

6、ReentrantLock源码剖析-tryAcquire方法

// 非公平锁实现finalbooleannonfairTryAcquire(int acquires){
    // 获取当前线程final Thread current = Thread.currentThread();
    // 拿到stateint c = getState();
    // 判断为0,为0就CASif (c == 0) {
        // 非公平锁直接CAS尝试将state从0改为1if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            // 代表锁资源拿到~~returntrue;
        }
    }
    // 有线程持有当前锁,查看是否是锁重入操作elseif (current == getExclusiveOwnerThread()) {
        // 如果是当前线程持有锁资源,进到else if,执行锁重入流程// 对state + 1int nextc = c + acquires;
        // 判断锁重入是否达到上限if (nextc < 0) 
            // 凉凉~thrownew Error("Maximum lock count exceeded");
        // 如果没有达到重入上线,修改state即可
        setState(nextc);
        returntrue;
    }
    // 没拿到锁,也不是锁重入,凉凉~returnfalse;
}

// 公平锁实现protectedfinalbooleantryAcquire(int acquires){
    final Thread current = Thread.currentThread();
    int c = getState();
    // state为0时,公平锁的操作if (c == 0) {
        // 查看AQS双向链表中是否有排队的Node// 如果没有排队的Node,直接抢锁// 如果有排队的Node,查看当前线程是不是优先级最高的Node的线程if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            returntrue;
        }
    }
    // 锁重入操作。。elseif (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            thrownew Error("Maximum lock count exceeded");
        setState(nextc);
        returntrue;
    }
    returnfalse;
}


publicfinalbooleanhasQueuedPredecessors(){
    // 拿到头和尾
    Node t = tail; 
    Node h = head;
    Node s;
    // 如果头尾不相等,证明node个数 > 1return h != t && 
        // 有Node在排队// 拿到head的next,赋值给s,s就是优先级最高的节点// 排在第一位的Node中的线程是不是当前线程?
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

7、ReentrantLock源码剖析-addWaiter方法

// addWaiter将当前thread封装为Node,并扔到AQS的双向链表中// mode:代表互斥。private Node addWaiter(Node mode){
    // 将当前thread封装为Node
    Node node = new Node(Thread.currentThread(), mode);
    // 拿到tail
    Node pred = tail;
    // 如果tail不为null,说明双向链表中有Node节点if (pred != null) {
        // 将当前Node扔到AQS双向链表中,
        node.prev = pred;
        // 修改tail指向时,是CAS操作if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 如果出现并发或者tail还没有初始化,执行enq方法
    enq(node);
    return node;
}

private Node enq(final Node node){
    // 死循环for (;;) {
        Node t = tail;
        // 如果tail为null,先初始化一个伪的head节点if (t == null) { 
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 你看上面代码
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

8、ReentrantLock源码剖析-acquireQueued方法

// 准备挂起线程// node: 当前线程Node// arg: 1finalbooleanacquireQueued(final Node node, int arg){
    // 拿锁失败标识:失败了么?是boolean failed = true;
    for (;;) {
        // 拿到当前节点的上一个节点final Node p = node.predecessor();
        // true:当前Node的位置是head.nextif (p == head && 
                // 如果是head的next,再次执行tryAcquire
                tryAcquire(arg)) {
            // 拿锁成功!当前Node变为head,并且设置thread和prev为null// 拿到锁的变伪节点了
            setHead(node);
            p.next = null; // help GC
            failed = false;
            returnfalse;
        }
        // 没有拿到锁资源,或者无法执行tryAcquire// 查看能否挂起线程,如果可以,返回true// 如果prev节点是1,代表取消,此时需要往前找到有效节点// 如果prev节点状态不是-1,需要先改为-1// 如果prev节点状态是-1,当前节点可以挂起// 为什么这么做?希望可以多执行几次tryAcquire,避免线程的挂起,因为线程挂起和唤醒需要用户态和内核态的切换,消耗性能if (shouldParkAfterFailedAcquire(p, node) &&
                // 挂起线程,执行Unsafe类中的park方法,挂起线程
                parkAndCheckInterrupt())
    }
}

// 查看当前线程能否挂起// 每一个Node都会有一个状态,封装在waitStatus属性中// 状态分为5个,只关注前三个/*
1:代表节点取消,不排了
0:代表刚来排队,初始化状态
-1:当前节点的next节点挂起了(park了,线程WAITING)
-2:Condition,await或者signal了
-3:为了解决JDK1.5的BUG,BUG是在Semaphore产生的。
*/privatestaticbooleanshouldParkAfterFailedAcquire(Node pred, Node node){
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        returntrue;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    returnfalse;
}

9、ReentrantLock源码剖析-release方法

publicfinalbooleanrelease(int arg){
    // 对state - 1,如果state减到0,代表锁资源完全释放干净if (tryRelease(arg)) {
        Node h = head;
        // 准备唤醒线程~~if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        returntrue;
    }
    returnfalse;
}

AQS为什么不用单向链表不可以吗?

因为排队的Node会出现取消操作,取消操作需要将当前节点的上一个节点的next执行当前节点的下一个节点,为了找到当前节点的上一个节点,需要从头遍历,成本太高了。

释放后,是拿链表中第一个?

释放的是head.next。就是第二个。

node对象 数据结构是咋样的?

Node对象里面就存储了几个属性,Node本身组成了一个双向链表,有prev,next。

为什么ReentrantLock中,head指向的Node是伪结点?

因为第一个节点是伪节点,伪节点不绑定任何线程,只有head.next后面的才绑定线程

head.next后面的才绑定线程?

因为在执行release方法时,需要判断是否需要唤醒线程。通过head节点的状态来判断后续节点是否需要被唤醒,如果head节点的状态是-1,我才需要执行唤醒后面挂起的线程。

ConcurrentHashMap在查询数据时,针对并发情况(有线程在写数据),是如何查询的?

查询数组上的数据,并发没问题

查询链表上的数据,并发也没问题

但是查询红黑树的上的数据就有问题了,因为如果存在写数据,红黑树会为了保证平衡,出现左旋和右旋操作,导致红黑树结构变化,查询就不能直接查询红黑树,他会保留一个双向链表,这是会查询双向链表

ConcurrentHashMap的size方法没有加锁,如何保证数据不出问题?(ConcurrentHashMap的addCount如何实现的)

ConcurrentHashMap中采用了类似LongAdder方式记录元素个数,内部除了基本的baseCount之后,还会有一个CounterCell[]存储着元素个数,执行size方法值需要将baseCount拿到,并且遍历CounterCell数据拿到全部数据即可。

线程池中执行addWorker时,为什么会添加一个任务为null的非核心线程?

因为执行这个方法前,会判断阻塞队列有任务,但是没有工作线程,这就会导致阻塞队列中的任务没有工作线程可以处理,一直卡在这个位置,导致任务阻塞了,所以会添加一个空任务的非核心线程处理阻塞队里的任务

原文地址:https://cloud.tencent.com/developer/article/2116059

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340