java面试JDK8 new ReentrantLock()加锁流程解析

 更新时间:2023年07月06日 10:27:12   作者:子瞻  
这篇文章主要为大家介绍了java面试JDK8 new ReentrantLock()加锁流程解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

new ReentrantLock() 加锁流程

//默认执行NonfairSync.lock();
final void lock() {
            //cas 0 -> 1 ,如果操作成功,将当前线程设置为独占线程
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //如果操作失败
                acquire(1);
        }
public final void acquire(int arg) {
        //tryAcquire(arg)尝试获取锁,如果获取锁失败返回false,否则返回true
        if (!tryAcquire(arg) &&
            //尝试获得锁,如果当前线程被中断 返回 true,否则返回false
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //获得锁失败,且当前线程的中断状态为true,则重新去尝试
            selfInterrupt();
    }

tryAcquire()

tryAcquire()最终默认调用ReentrantLock.NonfairSync.nonfairTryAcquire();

final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取state值
            int c = getState();
            //0 代表没有线程占用锁
            if (c == 0) {
                //cas 将 0 改为 1
                if (compareAndSetState(0, acquires)) {
                    //cas 成功之后 将独占线程改为当前线程
                    setExclusiveOwnerThread(current);
                    //返回成功
                    return true;
                }
            }
            //如果有线程获取当前线程(也就是当前线程是独占线程)
            else if (current == getExclusiveOwnerThread()) {
                //state值加1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //保存新的state值
                setState(nextc);
                //返回true
                return true;
            }
            //否则,就返回false
            return false;
        }

AbstractQueuedSynchronizer.addWaiter(Node.EXCLUSIVE)

解析

private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        //尾节点赋值给pred;
        Node pred = tail;
        //尾节点不为空
        if (pred != null) {
            //当前的节点prev指针指向现在的尾节点
            node.prev = pred;
            //cas 将当前尾节点 替换为 node节点
            if (compareAndSetTail(pred, node)) {
                //当前尾节点的next指针指向 node节点
                pred.next = node;
                return node;
            }
        }
        //如果尾节点为空或者cas替换失败,则执行入队操作
        enq(node);
        return node;
    }
    //入队操作:将当前节点设置为尾节点,并更新当前节点和替换前的尾节点的指针指向
    private Node enq(final Node node) {
            for (;;) {
                //把尾节点赋值给一个变量
                Node t = tail;
                //如果尾节点为空
                if (t == null) {
                    //新建一个节点,设置为头节点
                    if (compareAndSetHead(new Node()))
                        //把头节点赋值尾节点
                        tail = head;
                } else {
                    //如果尾节点不为空,当前节点的prev指针指向尾节点
                    node.prev = t;
                    //cas 将当前节点设置为尾节点
                    if (compareAndSetTail(t, node)) {
                        //替换前的尾节点的next指针指向当前节点
                        t.next = node;
                        return t;
                    }
                }
                //如果cas 替换尾节点 失败,则循环执行,直到成功为止
            }
      }

AbstractQueuedSynchronizer.acquireQueued(addWaiter(Node.EXCLUSIVE), arg);

//已经入队的非中断线程再次尝试获取锁
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获取当前节点的前一个节点信息
                final Node p = node.predecessor();
                //如果前一个节点信息为头节点,并且成功获得锁
                if (p == head && tryAcquire(arg)) {
                    //设置当前节点为头节点
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    //返回当前节点的线程中断状态
                    return interrupted;
                }
                //如果当前节点的前驱节点不是头节点 或者 获得锁失败,则阻塞当前节点线程等待当前节点的线程被唤醒,并判断中断状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    //阻塞当前线程,等待当前线程被唤醒后,判断当前线程的中断状态
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire(p, node)

细节如下:

//获取锁失败的线程检查并更新node中的waitStatus。如果线程应该被阻塞返回true。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取前一个节点的等待状态
        int ws = pred.waitStatus;
        //-1,代表前一个节点被阻塞中,需要唤醒
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            // > 0 代表前一个节点被取消,就递归往找waitStatus > 0的节点信息
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            //找到后,将此节点的next指针指向当前节点
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             * waitStatus 必须是0 或者 -3(表示可以共享获得)。表明我们需要唤醒,但是还没有阻塞。调用者需要重试去保证在阻塞操作之前不能获取成功
             */
            //cas 将 前一个节点的waitStatus 改为 -1(需要唤醒)
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //返回false
        return false;
    }
    private final boolean parkAndCheckInterrupt() {
        //阻塞当前线程
        LockSupport.park(this);
        //当前线程被唤醒后,判断当前线程的的中断状态
        //这里有一个非常重要的知识点:唤醒阻塞线程的方式 1.unpark 2.interrupt
        return Thread.interrupted();
    }

以上就是java面试JDK8 new ReentrantLock()加锁流程解析的详细内容,更多关于java JDK8 new ReentrantLock的资料请关注脚本之家其它相关文章!

相关文章

  • Spring rest接口中的LocalDateTime日期类型转时间戳

    Spring rest接口中的LocalDateTime日期类型转时间戳

    这篇文章主要介绍了Spring rest接口中的LocalDateTime日期类型转时间戳的方法,Java程序中一般将日期类型定义为LocalDateTime,数据库中保存的时间是0时区的时间
    2023-03-03
  • SpringCloud Alibaba项目实战之nacos-server服务搭建过程

    SpringCloud Alibaba项目实战之nacos-server服务搭建过程

    Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。本章节重点给大家介绍SpringCloud Alibaba项目实战之nacos-server服务搭建过程,感兴趣的朋友一起看看吧
    2021-06-06
  • Java表格JTable代码实例解析

    Java表格JTable代码实例解析

    这篇文章主要介绍了Java表格JTable代码实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Java使用跳转结构实现队列和栈流程详解

    Java使用跳转结构实现队列和栈流程详解

    这篇文章主要介绍了Java使用跳转结构实现队列和栈流程,连续结构和跳转结构是数据结构中常见的两种基本数据结构,而我们本次的主角栈和队列都 既可以使用使用跳转结构实现也可以使用连续结构实现
    2023-04-04
  • java web response提供文件下载功能的实例讲解

    java web response提供文件下载功能的实例讲解

    下面小编就为大家分享一篇java web response提供文件下载功能的实例讲解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-01-01
  • SpringBoot使用RestTemplate如何通过http请求将文件下载到本地

    SpringBoot使用RestTemplate如何通过http请求将文件下载到本地

    文章介绍了如何通过编写代码批量下载文件,解决了没有提供批量下载接口的问题,首先筛选出需要下载的文件ID,然后通过后端代码发起HTTP请求,将下载的资源写入本地文件中,总结了实现方式和注意事项,希望能为类似任务提供参考
    2025-02-02
  • Springboot连接和操作mongoDB方式

    Springboot连接和操作mongoDB方式

    这篇文章主要介绍了Springboot连接和操作mongoDB方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • java实现航班信息查询管理系统

    java实现航班信息查询管理系统

    这篇文章主要为大家详细介绍了java实现航班信息查询管理系统,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • java读取html文件,并获取body中所有的标签及内容的案例

    java读取html文件,并获取body中所有的标签及内容的案例

    这篇文章主要介绍了java读取html文件,并获取body中所有的标签及内容的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • 浅谈Java8对字符串连接的改进正确姿势

    浅谈Java8对字符串连接的改进正确姿势

    这篇文章主要介绍了Java8:对字符串连接的改进,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10

最新评论