Java并发之ReentrantLock

news2024/10/5 15:33:10

AQS

AQS(AbstractQueuedSynchronizer):抽象队列同步器,是一种用来构建锁和同步器的框架。在是JUC下一个重要的并发类,例如:ReentrantLock、Semaphore、CountDownLatch、LimitLatch等并发都是由AQS衍生出来的。

理解

CLH队列

是一种基于链表的可扩展,高性能,公平的自旋锁。它的队列中每个节点等待前驱节点释放锁,当前置节点执行完成,才会唤醒后置节点,这样最多只有后置节点和新进入的线程(非公平锁状态下)来抢占CPU资源,其余线程处于阻塞状态,极大地减少了CPU开销(同时将多个线程唤醒时,唤醒的线程将进入RUNABLE状态,但是只有一个线程会从竞争获取到锁,而其他线程将会处于BLOCKED状态)。

CLH的出列

当node1出列时,就会把当head的指向出队的下个节点node2,同时也把node1的上下关系断开。
在这里插入图片描述

CLH的入列

队列初始化的时候,head和tail都为空。此时有节点入列,这把当前节点存放到队列尾部节点的下个节点中,在让tail指向这个新增节点,同时也会处理入队的节点的上下节点关系。
在这里插入图片描述

CAS

全称为Compare-And-Swap,它是一条CPU并发原语,也是一种乐观锁(类似数据库设计表时添加的version字段)。执行过程:如果当前状态值(内存中在stateOffset位置的值)等于预期值(expect),则自动将同步状态设置为给定的更新值(update);反之则重试(好像是10次),重试后依旧无果,那么就更新失败。

    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        // stateOffset指向一段内存,通过这个可以准确地告诉你某个字段相对于对象的起始内存地址的字节偏移,便于比较。
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

过程

  • 抢锁
    公平锁
    按照线程锁的添加顺序(由CLH来保证)来获取锁。

    public final void acquire(int arg) {
        if (!tryAcquire(arg) && // 尝试获取锁
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 获取锁失败,添加到队列中
            selfInterrupt();// 添加到队列成功后,就进行中断状态
    }
    
    // 返回false时,获取锁失败,返回true,获取成功
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState(); // 获取锁的状态state
        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;
    }
    

    非公平锁
    等待队列中的首位正要获取的锁的线程(没有添加到队列中),有同等的机会获取锁。

    final void lock() {
        if (compareAndSetState(0, 1))//尝试修改锁的状态
            setExclusiveOwnerThread(Thread.currentThread());// 修改成功,就把自己设置成独占访问权限
        else
            acquire(1); // 就加入等待队列中
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);// 已非公平锁的方式获取锁,
    }
    // 
    final boolean nonfairTryAcquire(int acquires) {
                final Thread current = Thread.currentThread();
                int c = getState();
                if (c == 0) {// 处于空闲时,和队列中等待的线程竞争获取锁。
                    if (compareAndSetState(0, acquires)) {
                        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;
            }
    
  • 释放锁
    asdfasd

  • 入队、阻塞

      // 线程入列
      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);
          }
      }
      // 判断节点状态,用于判断是否可以中断,同时处理掉线程中已经中断的节点
      private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)// 正常阻塞的节点 SIGNAL=-1
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {// 节点状态错误,删除掉这样的节点
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);// 将前置节点修改位-1,表示后续节点将被阻塞。
        }
        return false;
    }
    
    // 队列是否有前置线程   
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
        // h = t时,队列中至少有两个队列(未初始化的时候,h,t都为null,说明没有前执行的线程,返回false;如多队列中只有一个线程的时候,h=t, 这个线程一定正在执行,返回false;队列中存在多个时,
        // h != t时,(s = h.next) == null 这个条件可能是为了在end()中,给head设置new Node时,还未将head赋值给tail时做的处理,s.thread != Thread.currentThread()用于判断是不是自旋)
    }
    
    // 添加到队列中
    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);// 兜底的手段:1)一定要把当前节点添加到队列中 2)对位未初始化,初始化队列中
        return node;
    }
    // 通过死循环添加到队列中
     private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))// 头节未nul时,增加一个空的新节点
                    tail = head;// 让尾节点等于头节点
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {// 把当前节点添加到未节点
                    t.next = node;
                    return t;
                }
            }
        }
    }
    
  • 唤醒

// 释放锁
public void unlock() {
    sync.release(1);
}
// 释放锁的具体实现
public final boolean release(int arg) {
    if (tryRelease(arg)) {// 释放成功后,
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);//唤醒当前的头结点
        return true;
    }
    return false;
}
// 尝试释放
protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())// 释放锁的线程和获取锁的线程不同,抛出异常
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {// 为0是,说明锁处于空闲状态
        free = true;
        setExclusiveOwnerThread(null);// 修改当前获取锁的线程为空
    }
    setState(c);// 修改锁的状态
    return free;// c != 0时,说明自选还未完成,释放锁失败。
}

// 唤醒下个节点
private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)// 节点的线程状态处于等待中,就更新为0.
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {// 线程可能超时,或者中断了,无法在唤醒了
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)// 从队列尾部开始唤醒可以获取锁的线程,直到上一个完成的线程(t).这里从尾部开始的原因可能是越早的线程越容易超时(中断),所以从尾部获取比较快速。
            if (t.waitStatus <= 0)// 线程状态必须<=0。由于线程在由初始化0变成-1时,不是一个原子,所以这里位<=0
                s = t;
    }
    if (s != null)//  当线程存在时,唤醒线程
        LockSupport.unpark(s.thread);
}
  • 出列
    在添加队列的时候调用acquireQueued时,会将head出列,交给gc回收
    在这里插入图片描述

机制

  • state
    锁的状态:
    • 0:锁处于空闲状态
    • >=1: 已经有线程获取到锁了(大于1 自选)
	 /**
	  * The synchronization state.
	  */
	 private volatile int state;
  • waitState
    线程状态:
    • -1:表示该节点的后继节点被阻塞(或即将被阻塞),因此当前节点在释放或取消时必须解除其后继节点的阻塞。
    • 0:初始化的状态
    • -2:条件状态(暂时不清楚)
    • >=1:表示由于超时或中断,此节点被取消,节点永远不会离开这种状态。取消了节点的线程不会再阻塞其它线程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/879119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ElementUI的MessageBox的按钮置灰且不可点击

// this.$confirmthis.$alert(这是一段内容, 标题名称, {confirmButtonText: 确定,confirmButtonCLass: confirmButton,beforeClose: (action,instance,done) > {if (action confirm) {return false} else {done()}});}.confirmButton {background: #ccc !important;cursor…

淘宝API接口的实时数据和缓存数据区别

电商API接口实时数据是指通过API接口获取到的与电商相关的实时数据。这些数据可以包括商品库存、订单状态、销售额、用户活跃度等信息。 通过电商API接口&#xff0c;可以实时获取到电商平台上的各种数据&#xff0c;这些数据可以帮助企业或开发者做出及时的决策和分析。例如&…

16.3.1 【Linux】程序的观察

既然程序这么重要&#xff0c;那么我们如何查阅系统上面正在运行当中的程序呢&#xff1f;利用静态的 ps 或者是动态的 top&#xff0c;还能以 pstree 来查阅程序树之间的关系。 ps &#xff1a;将某个时间点的程序运行情况撷取下来 仅观察自己的 bash 相关程序&#xff1a; p…

安达发APS|APS排产软件之计划甘特图

在当今全球化和竞争激烈的市场环境下&#xff0c;制造业企业面临着巨大的压力&#xff0c;如何在保证产品质量、降低成本以及满足客户需求的同时&#xff0c;提高生产效率和竞争力成为企业需要迫切解决的问题。在这个背景下&#xff0c;生产计划的制定和执行显得尤为重要。然而…

Spring Boot 集成 XXL-JOB 任务调度平台

一、下载xxl-job并使用。 二、将xxl-job集成到springboot里 一、 下载xxl-job并使用。 这一步没完成的请参考这个博客&#xff1a;http://t.csdn.cn/lsp4r 二、将xxl-job集成到springboot里 1、引入依赖 <dependency><groupId>org.springframework.boot</group…

单模光纤模场强度分布以及高斯近似的MATLAB仿真

已知纤芯半径5um&#xff0c;数值孔径NA 0.1&#xff0c;波长 用波长和数值孔径计算归一化常数V 之前我们在单模光纤特征方程及其MATLAB数值求解中&#xff0c;用线性关系拟合过V和W&#xff0c;这里直接用拟合结果 U用V和W计算 clc clear close alla 5e-6;%纤芯半径 NA …

ssm+vue小型企业办公自动化系统源码和论文PPT

ssmvue小型企业办公自动化系统源码和论文PPT013 开发工具&#xff1a;idea 数据库mysql5.7(mysql5.7最佳) 数据库链接工具&#xff1a;navcat,小海豚等 开发技术&#xff1a;java ssm tomcat8.5 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xf…

Web服务器项目一

文章目录 是什么HTTP协议——应用层协议服务器基本框架两种高效的处理模式线程池 是什么 Web服务器是一个服务器软件程序&#xff0c;主要功能是通过HTTP协议与客户端&#xff08;通常是浏览器Browser)进行通信&#xff0c;来接收&#xff0c;存储&#xff0c;处理来自客户端的…

npm run xxx 的时候发生了什么?(以npm run dev举例说明)

文章目录 一、去package.json寻找scripts对应的命令二、去node_modules寻找vue-cli-service三、从package-lock.json获取.bin的软链接1. bin目录下的那些软连接存在于项目最外层的package-lock.json文件中。2.vue-cli-service文件的作用3.npm install 的作用 总结 一、去packag…

java springboot驾校管理系统

开发技术&#xff1a; SpringBootspringmvcMybitis-Puls mysql5.x html ajax数据交互 开发工具&#xff1a; idea或eclipse jdk1.8 maven (1) 管理员登陆 (2) 所有学员信息列表查询 &#xff08;3&#xff09;所有学生考试信息 &#xff08;4&#xff09;学员…

Datawhale Django入门组队学习Task01

Task01 一.创建虚拟环境 python -m venv django_learn &#xff08;django_learn那里是自己定的环境名字&#xff09; 之前一直用conda管理虚拟环境&#xff0c;没咋用过virtualenv&#xff0c;然后我的powershell之前也设置了默认启动了base环境&#xff0c;然后输入activat…

目标检测(Object Detection)

文章目录 1. 目标检测1.1 目标检测简要概述及名词解释1.2 IOU1.3 TP TN FP FN1.4 precision&#xff08;精确度&#xff09;和recall&#xff08;召回率&#xff09; 2. 边框回归Bounding-Box regression3. Faster R-CNN3.1 Faster-RCNN&#xff1a;conv layer3.2 Faster-RCNN&…

如何搭建个人邮件服务hmailserver并实现远程发送邮件

文章目录 1. 安装hMailServer2. 设置hMailServer3. 客户端安装添加账号4. 测试发送邮件5. 安装cpolar6. 创建公网地址7. 测试远程发送邮件8. 固定连接公网地址9. 测试固定远程地址发送邮件 hMailServer 是一个邮件服务器,通过它我们可以搭建自己的邮件服务,通过cpolar内网映射工…

Destination Host Unreachable

背景&#xff1a;物理机的IP地址是192.168.31.189&#xff0c;虚拟机的IP地址是192.168.194.130 物理机ping得通虚拟机 虚拟机ping得通外网 可是虚拟机ping不通物理机 1、报错信息 Destination Host Unreachable 2、原因 用route -n命令查看路由表发现192.168.194.0没有走网…

设计HTML5图像和多媒体

在网页中的文本信息直观、明了&#xff0c;而多媒体信息更富内涵和视觉冲击力。恰当使用不同类型的多媒体可以展示个性&#xff0c;突出重点&#xff0c;吸引用户。在HTML5之前&#xff0c;需要借助插件为网页添加多媒体&#xff0c;如Adobe Flash Player、苹果的QuickTime等。…

R语言画图的-- ggplot2(实现图例的精细修改)

文章目录 1. 图的精确修改theme函数实现图的精细修改实现一页多图具体作图中的参数修改(某些特殊的参数)柱状图的参数修改 ggplot2是R中用来作图的很强的包&#xff0c;但是其用法比较大且各种参数比较复杂&#xff0c;我自己使用的时候还经常需要查阅一些关键参数等&#xff0…

Stable Diffusion + Deform制作指南

1.安装sd以及deform插件,更新后记得重启 需要安装ffmpeg https://ffmpeg.org/download.html 选择对应版本然后安装 如果是windows需要解压后将ffmpeg的bin目录配置在电脑的环境变量里面。 2.准备一张初始开始图片 3.填写参数,这里面参数要注意,宽高一定是32的倍数。如果填写…

R语言生存分析算法的简单组合

library(survival) library(randomForestSRC)# 生成模拟数据 set.seed(123) n <- 200 time <- rexp(n, rate 0.1) status <- rbinom(n, size 1, prob 0.7) var1 <- rnorm(n) var2 <- rnorm(n) var3 <- rnorm(n) data1 <- data.frame(time time, statu…

「网络」网络安全必须知道的19个知识分享

一、防火墙&#xff08;Firewall&#xff09; 定义&#xff1a;都知道防火墙是干什么用的&#xff0c;但我觉得需要特别提醒一下&#xff0c;防火墙抵御的是外部的攻击&#xff0c;并不能对内部的病毒 ( 如ARP病毒 ) 或攻击没什么太大作用。 功能 : 防火墙的功能主要是两个网…