当前位置: 首页 > article >正文

JUC-AQS

如何利用CAS实现一个同步框架

  • state:共享标记位。利用CAS修改,达到同步管理
  • 等待队列:存储需要等待获取锁的线程

共享标记位state=0 表示资源是空闲的;state=1表示有1个线程获取到资源,如何独占模式,判断持有锁的线程是否是当前线程,若是,则state变为2,达到可重入性

 如果获取锁失败立即返回,则不需要入队

如果需要不断的尝试,业务侧可循环适用用tryLock不断重试

AQS作用

定义了加锁、释放锁的骨架

AbstractQueuesSynchorizer属性解释

Queues:队列

Synchorizer: 同步阻塞

volatile int state

        用于记、判断共享资源正在被占用的情况

为什么适用int而不是boolean?

为了可重入(reentrantLock多次加锁,state需要一致递增,最后需要对称释放锁)

为了共享锁模式

volatile Node head、tail

        头尾节点:线程没有获取到锁,需要排队

Node 的属性

  • Node prev、next  双向链表
  • Thread thread    当前入队等待的线程
  • Node nextWaiter     
  • int waitStatus  等待状态   1, -1, -2, -3

  核心方法

以下方法是骨架,实际的实现由子类完成

tryAcquire(int arg)

tryRelease(int arg)

acquire(int arg)

      获取锁

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

  获取锁失败,则进入等待队列

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            // 尝试快速入队
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 完整的入队操作
        enq(node);
        return node;
    }

if (compareAndSetTail(pred, node)) {
    pred.next = node;
    return node;
}

问题: compareAndSetTail 是原子的,但是整个if语句不是原子的,为什么不会出问题?

compareAndSetTail是通过原子的方式设置尾节点(此时包含了tail=node:将当前的node节点设置尾tail),pred.next = node;只是设置之前的tail节点的next节点,这个操作不会有其他线程干扰

问题:    为什么要使用 Node pred = tail?

因为tail节点的指针会被其他线程修改,类似写时复制,如果tail节点改变,pred的指针还是指向原来的tail节点

问题:   为什么要先尝试快速入队,而不是直接入队

应该是测试的结果,认为tryAcquire失败,大概率尾节点不为空,先尝试入队,如果成功,则少了一步完整队逻辑中的 tail为null的判断

完整的入队(循环cas直到入队成功)

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

对线程进程挂起与响应(推进队列中的线程进行消费)   

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {

                // 获取当前节点的上一个节点
                final Node p = node.predecessor();

                // 上一个节点是head节点 && 尝试获取锁成功
                // head节点是一个虚节点,当前节点获取锁后,变成了head节点
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }

                // 判断是否需要挂起,避免过度自旋,消耗CPU
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 acquire完整的流程

  • 先tryAcquire获取锁,获取锁失败,则进入等待队列
  • 先尝试“快速入队”,失败后在使用“完整入队”。(完整入队比快速入队多判断了一个tail节点是否为null,因为tryAcquire失败,大概率tail不为null,使用快速入队能提高性能)
  • 自旋获取锁(判断当前节点的前置节点是否是head节点,并且尝试获取锁,获取失败一直重试)
  • 线程挂起(重试一定次数,线程会挂起,避免无效的消耗CPU)

release(int arg)

    public final boolean release(int arg) {

         // 尝试释放锁
        if (tryRelease(arg)) {
            Node h = head;

            // 唤醒等待的线程
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

唤醒线程unpark

    private void unparkSuccessor(Node node) {

        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;

              // 从后往前找第一个 waitStatus <=0的
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

加锁、入队、出队、挂起、唤醒

问题:为什么从后往前找?

enq入队方法,从tail尾入队,compareAndSetTail将当前节点设置为tail后,FIFO的倒数第二个节点 ->tail还没有创建链接(t.next = node),如果从前往后找,可能会漏掉此节点。这也是为什么 node.prev = t 在 compareAndSetTail 之前的原因

node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}


http://www.kler.cn/news/155569.html

相关文章:

  • 如何在 vue 项目中创建 svg 组件
  • 国标GB28181协议/RTSP视频监控汇聚平台EasyCVR(V.3.4)页面UI大更新
  • 【代码随想录算法训练营-第二天】【数组】977.有序数组的平方 ,209.长度最小的子数组 ,59.螺旋矩阵II
  • Vue中mvvm的作用
  • SpringCloudAlibaba微服务 【实用篇】| Nacos配置管理
  • 【LangChain实战】开源模型学习(2)-ChatGLM3
  • React如何检查组件性能
  • 使用Pytoch实现Opencv warpAffine方法
  • sourceTree的下载和安装
  • java高校实验室排课学生考勤系统springboot+vue
  • 【数据结构高阶】AVL树
  • 跟着GPT学习shell脚本,理论与实践相结合的学习计划。
  • 页面表格高度自适应
  • UIkit-UIAlertContent
  • Django之ORM
  • 1、输入一行字符,分别统计出其中的英文字母、空格、数字和其他字符的个数。
  • Google Guava 区间工具使用详解
  • Spring的创建
  • 【滑动窗口】水果成篮
  • Python如何优雅地使用重试:tenacity
  • C++11——initializer_list
  • 初识主力投资者
  • Linux MeterSphere一站式开源持续测试平台远程访问
  • JavaSE学习路线及经验所谈
  • selenium使用记录
  • 1949-2021年全国31省公路里程数据
  • Docker容器(一)概述
  • Python安装步骤介绍
  • LeetCode 每日一题 Day1
  • 软件工程导论学习资料