Java——并发编程(CAS、Lock和AQS)

news2024/11/29 22:51:24

1、Java Concurrent API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优势?

答:Lock 接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。

优势:

  • 可以使锁更公平;
  • 可以使线程在等待锁的时候响应中断;
  • 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间;
  • 可以在不同的范围,以不同的顺序获取和释放锁;

整体上来说 Lock synchronized 的扩展版Lock 提供了无条件的、可轮询的(tryLock()方法)、定时的(tryLock() 带参方法)、可中断的(lockInterruptibly())、可多条件队列的(newCondition() 方法)锁操作。另外Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。

2、什么是CAS

  • CAScompare and swap 的缩写,即我们所说的比较交换
  • CAS是一种基于锁的操作,而且是乐观锁。在 Java 中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加 version 来获取数据,性能较悲观锁有很大的提高。
  • CAS 操作包含三个操作数 ——内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A的值是一样的,那么就将内存里面的值更新成BCAS 是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被b线程修改了,那么a 线程需要自旋,到下次循环才有可能机会执行。
  • java.util.concurrent.atomic 包下的类大多是使用 CAS 操作来实现的(AtomicInteger,AtomicBoolean,AtomicLong)

3、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

  • 悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如 Java 里面的同步原语synchronized 关键字的实现也是悲观锁。
  • 乐观锁: 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供。乐观锁。在Java java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式CAS 实现的。

3.1、乐观锁的实现方式:

  • 使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略。
  • Java 中的 Compare and Swap CAS ,当多个线程尝试使用 CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

3.2、CAS 操作中包含三个操作数

  • 需要读写的内存位置(V
  • 进行比较的预期原值(A
  • 拟写入的新值(B)

如果内存位置 V 的值与预期原值 A 相匹配,那么处理器会自动将该位置值更新为新值 B。否则处理器不做任何操作。

4、CAS 的会产生什么问题?

  • ABA 问题: 比如说一个线程 one 从内存位置 V 中取出 A,这时候另一个线程two 也从内存中取出 A,并且two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行CAS 操作发现内存中仍然是 A,然后 one 操作成功。尽管线程 oneCAS 操作成功,但可能存在潜藏的问题。从Java1.5 开始 JDKatomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
  • 循环时间长开销大: 对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU资源,效率低于synchronized
  • 只能保证一个共享变量的原子操作: 当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁。

5、AQS

答:AQS 队列同步器是用来构建锁或其他同步组件的基础框架,它使用一个 volatile int state 变量作为共享资源,如果线程获取资源失败,则进入同步队列等待;如果获取成功就执行临界区代码,释放资源时会通知同步队列中的等待线程。比如我们提到的ReentrantLockSemaphore,其他的诸如ReentrantReadWriteLockSynchronousQueueFutureTask等等皆是基于AQS的。

同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,对同步状态进行更改需要使用同步器提供的 3个方法 getState()setState()compareAndSetState() ,它们保证状态改变是安全的。子类推荐被定义为自定义同步组件的静态内部类,同步器自身没有实现任何同步接口,它仅仅定义若干同步状态获取和释放的方法,同步器既支持独占式也支持共享式。同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。锁面向使用者,定义了使用者与锁交互的接口,隐藏实现细节;同步器面向锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理、线程排队、等待与唤醒等底层操作。

每当有新线程请求资源时都会进入一个等待队列,只有当持有锁的线程释放锁资源后该线程才能持有资源。等待队列通过双向链表实现,线程被封装在链表的 Node 节点中,Node 的等待状态包括:

  • CANCELLED(线程已取消)
  • SIGNAL(线程需要唤醒)
  • CONDITION(线程正在等待)
  • PROPAGATE(后继节点会传播唤醒操作,只在共享模式下起作用)。

6、AQS的原理?

  • 获取同步状态: 调用 acquire() 方法,维护一个同步队列,使用 tryAcquire() 方法安全地获取线程同步状态,获取失败的线程会被构造同步节点并通过 addWaiter() 方法加入到同步队列的尾部,在队列中自旋。之后调用 acquireQueued() 方法使得该节点以死循环的方式获取同步状态,如果获取不到则阻塞,被阻塞线程的唤醒主要依靠前驱节点的出队或被中断实现,移出队列或停止自旋的条件是前驱节点是头结点且成功获取了同步状态。
  • 释放同步状态: 同步器调用tryRelease() 方法释放同步状态,然后调用 unparkSuccessor() 方法唤醒头节点的后继节点,使后继节点重新尝试获取同步状态。

6.1、CLH队列:

答: CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

6.2、AQS原理图

答: 获取同步状态时,调用 acquire() 方法,维护一个同步队列,使用 tryAcquire() 方法安全地获取线程同步状态,获取失败的线程会被构造同步节点并通过 addWaiter() 方法加入到同步队列的尾部,在队列中自旋。之后调用 acquireQueued() 方法使得该节点以死循环的方式获取同步状态,如果获取不到则阻塞,被阻塞线程的唤醒主要依靠前驱节点的出队或被中断实现,移出队列或停止自旋的条件是前驱节点
是头结点且成功获取了同步状态。

释放同步状态时,同步器调用 tryRelease() 方法释放同步状态,然后调用 unparkSuccessor() 方法唤醒头节点的后继节点,使后继节点重新尝试获取同步状态。

在这里插入图片描述

AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。

private volatile int state;//共享变量,使用volatile修饰保证线程可见性

状态信息通过protected类型的getState()setState()compareAndSetState()进行操作:

//返回同步状态的当前值
protected final int getState() { 
    return state;
}
// 设置同步状态的值
protected final void setState(int newState) {
    state = newState;
}
//原子地(CAS操作)将同步状态值设置为给定值update如果当前同步状态的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

6.3、AQS对资源的共享方式

答:AQS定义两种资源共享方式,独占模式通过 acquirerelease 方法获取和释放锁,共享模式通过acquireSharedreleaseShared 方法获取和释放锁。

  • Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
    • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁;
    • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的。
  • Share(共享):多个线程可同时执行,如Semaphore/CountDownLatchSemaphoreCountDownLatchCyclicBarrierReadWriteLock 我们都会在后面讲到。

ReentrantReadWriteLock 可以看成是组合式,因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。

不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。

6.4、AQS底层使用了模板方法模式

答: 同步器的设计是基于模板方法模式的,如果需要自定义同步器一般的方式是这样(模板方法模式很经典的一个应用):

  • 使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放)
  • AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

6.5、AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法。

//该线程是否正在独占资源。只有用到condition才需要去实现它。
isHeldExclusively()
//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryAcquire(int)
//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryRelease(int)
//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryAcquireShared(int)
//共享方式。尝试释放资源,成功则返回true,失败则返回false。
tryReleaseShared(int)

答: 默认情况下,每个方法都抛出 UnsupportedOperationException 。 这些方法的实现必须是内部线程安全的,并且通常应该简短而不是阻塞。AQS类中的其他方法都是final ,所以无法被其他类使用,只有这几个方法可以被其他类使用:

  • ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
  • 再以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,stateCAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()调用主线程,然后主调用线程就会从await()函数返回,继续后面动作。

一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现 tryAcquire-tryReleasetryAcquireShared-tryReleaseShared 中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如 ReentrantReadWriteLock

7、为什么只有前驱节点是头节点时才能尝试获取同步状态?

答: 头节点是成功获取到同步状态的节点,后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节
点。

目的:维护同步队列的 FIFO 原则,节点和节点在循环检查的过程中基本不通信,而是简单判断自己的前驱是否为头节点,这样就使节点的释放规则符合 FIFO,并且也便于对过早通知的处理,过早通知指前驱节点不是头节点的线程由于中断被唤醒。

8、AQS共享式式获取/释放锁的原理?

答:

  • 同步状态: 调用 acquireShared() 方法,该方法调用 tryAcquireShared() 方法尝试获取同步状态,返回值为int 类型,返回值不小于0 表示能获取同步状态。因此在共享式获取锁的自旋过程中,成功获取同步状态并退出自旋的条件就是该方法的返回值不小于0。
  • 释放同步状态: 调用 releaseShared() 方法,释放后会唤醒后续处于等待状态的节点。它和独占式的区别在于 tryReleaseShared() 方法必须确保同步状态安全释放,通过循环 CAS 保证,因为释放同步状态的操作会同时来自多个线程。

9、什么是可重入锁(ReentrantLock)?

答:ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。

在Java关键字synchronized隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入。与此同时,ReentrantLock还支持公平锁和非公平锁两种方式。

重入性的实现原理

答: 要想支持重入性,就要解决两个问题:

  1. 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;

  2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。

ReentrantLock支持两种锁:公平锁和非公平锁。何谓公平性,是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求上的绝对时间顺序,满足FIFO

10、ReadWriteLock 是什么?

答: 如果使用 ReentrantLock,可能本身是为了防止线程 A在写数据、线程 B 在读数据造成的数据不一致,但这样,如果线程 C 在读数据、线程 D 也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。因为这个,才诞生了读写锁ReadWriteLock。

ReadWriteLock 是一个读写锁接口,读写锁是用来提升并发程序性能的锁分离技术,ReentrantReadWriteLockReadWriteLock 接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。
而读写锁有以下三个重要的特性:

  • 公平选择性: 支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
  • 重进入: 读锁和写锁都支持线程重进入。
  • 锁降级: 遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。

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

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

相关文章

Scala中类的继承、抽象类和特质

1. 类的继承 1.1 Scala中的继承结构 Scala 中继承关系如下图: Any 是整个继承关系的根节点; AnyRef 包含 Scala Classes 和 Java Classes,等价于 Java 中的 java.lang.Object; AnyVal 是所有值类型的一个标记; Nul…

AI:51-基于深度学习的电影评价

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌本专栏包含以下学习方向: 机器学习、深度学…

渗透测试工具(AntSword)

软件安装 蚁剑渗透测试工具分为AntSword-Loader和antSword两部分 文件下载: AntSword-Loader下载地址:GitHub - AntSwordProject/AntSword-Loader: AntSword 加载器 antSword下载地址:Releases AntSwordProject/antSword GitHub 使用&a…

公司内网知识问答库系统源码 完全开源可二次开发 带完整搭建教程

随着公司规模的扩大和业务复杂性的增加,员工需要更快更有效地获取和共享知识。一个内部的知识问答库系统可以帮助公司提高员工的工作效率和知识管理水平。 有效的内部沟通是公司成功的关键因素之一。通过创建一个内部的知识问答平台,可以鼓励员工之间的…

入门指南|机器人流程自动化(RPA)在数字营销中的8大应用

虽然现在的话题度不及ChatGPT,但近两年最火的MarTech工具非RPA莫属。今天我们就来看看:资本宠儿、号称世界500强中超过70%的企业都在使用、老板心中最佳员工RPA到底是什么?以及在营销与运营中有哪些应用? 01 RPA是什么&#xff1f…

Uni-App 快捷登录

uniapp 实现一键登录前置条件: 开通uniCloud, 开通一键登录功能参考的文档 : 官网 - 一键登录uniapp指南 : https://uniapp.dcloud.net.cn/univerify.html#%E6%A6%82%E8%BF%B0 官网 - 一键登录开通指南 : https://ask.dcloud.net.cn/article/37965 官网 - unicloud使用指南 htt…

如何使用JDBC操作数据库?一文带你吃透JDBC规范

文章目录 1. 前言2. JDBC 概述2.1 概念2.2 优点 3. JDBC 快速入门4. JDBC API详解4.1 DriverManager4.1.1 注册驱动4.1.2 获取连接 4.2 Connection4.2.1 获取执行sql的对象4.2.2 事务管理 4.3 Statement4.4 ResultSet4.5 PreparedStatement4.5.1 sql注入问题4.5.2 preparedStat…

不用动脑小白也能制作出精美的电子杂志

随着互联网技术的发展和微信的普及程度,电子杂志制作已经受到越来越多人的关注,毕竟电子杂志的阅读便利性是纸质杂志无法比拟的。那如何制作一本精美的电子杂志呢? 这其实很easy,可以使用在线电子杂志制作平台FLBOOK &#xff0c…

Linux使用挂载Windows共享文件夹

1、在linux下要挂载windows的共享文件,需要安装cifs-utils软件包。 dpkg -l|grep cifs-utils可以查看是否安装了这个软件包,which mount.cifs可以查看这个软件包安装在哪里。sudo apt-get install cifs-utils可以安装软件包。 2、建立一个目录用来作挂载…

AIR700 改变AIR530Z串口波特率

AIR530Z串口波特率默认9600,但实际应用中需要更高的波特率,可选波特率见下图。 主控采用合宙AIR700E核心板,通过UART1连接AIR530Z 合宙Luatos uart官方文档 主要问题 AIR700E要改变AIR530Z的波特率,在默认9600波特率情况下通过发…

跨境电商商城源码(支持多种支付方式+多语言+多货币+快速部署)

随着跨境电商的繁荣发展,支付方式的多样性已成为消费者和电商平台关注的焦点。本文将介绍一款支持多种支付方式的跨境电商源码,以提升用户的购物体验和平台的竞争力。 一、背景概述 近年来,跨境电商市场呈现出迅速增长的趋势。然而&#xff0…

建设城市展厅用的多媒体互动装置有哪些作用?

随着科技的迅速进步,智慧城市这一概念已经逐渐从理论走向现实,而智慧城市展厅则成为了集中展示智慧城市理念、技术和规划的重要场所,在其中发挥着重要的作用,并且还在建设这些展厅的过程中,应用了大量的多媒体互动装置…

fpmarkets总结的交易员5个阶段,您处在第几级

交易员的发展是一个逐步上升的阶梯式过程。总体上,这个过程被fpmarkets总结的五个阶段: 第一阶段,新手期。在这个阶段,新手交易者会接触到大量的市场工具和信息。主要目标是学习如何在市场中交易,同时避免产生亏损。许…

MySQL:至少参与xxx参与的全部事件(二)

MySQL:至少参与xxx参与的全部事件(二) – WhiteNights Site 标签:MySQL 本来不难的,结果实验课上又没能当场做出来。还是回到宿舍复盘才看到问题所在,令人感慨。 头歌例题 仔细审题 任务描述 创建一个名…

数据结构(超详细讲解!!)第二十节 数组

1.定义 1.概念 相同类型的数据元素的集合。 记作:A(A0,A1,…,Am-1) 二维数组可看作是每个数据元素都是相同类型的一维数组的一维数组。多维数组依此类推。 二维数组是数据元素为线性表的线性表。 A(A0,A1,……,An-1) 其中…

docker 下安装mysql8.0

在docker中查询mysql镜像 PS C:\Users\admin> docker search mysql NAME DESCRIPTION STARS OFFICIAL AUTOMATED mysql MySQL is a widely used, open-source relation……

每个外贸人都有一颗不甘的心

是不是每个三十多岁的外贸人都有一颗不安现状的心?总是想挑战一下自己的极限或者是拓宽一下自己未知的领域? 最近遇到一个经常去各个国家参加展会来获取客户的外贸人,他的第一话题不是自己去展会的效果如何,也不是说自己现在做的…

更好看更好听的百元真无线耳机,BarbetSound Buds T60上手

现在的真无线蓝牙耳机已经很成熟了,选择也特别多,大家很容易找到适合自己的款式。我比较关注耳机在续航、音质等方面的表现,最近用的是来自BarbetSound的Buds T60,这款耳机轻巧便携,而且音质非常好,性价比十…

【LLM】预训练||两句话明白儿的底层原理

预训练鼻祖阶段:目前认为是Bert时期 从字面上看,预训练模型(pre-training model)是先通过一批语料进行训练模型,然后在这个初步训练好的模型基础上,再继续训练或者另作他用。这样的理解基本上是对的&#…

学习笔记|两组率卡方检验和Fisher确切法|适用条件|规范表达|《小白爱上SPSS》课程:SPSS第十五讲 | 两组率卡方检验和Fisher确切法怎么做?

目录 学习目的软件版本原始文档两组率卡方检验和Fisher确切法适用条件简述一、实战案例读数据: 二、统计策略三、SPSS操作四、结果解读第一,分组统计描述结果第二,卡方检验。 五、规范报告1、规范表格2、规范文字 学习目的 SPSS第十五讲 | 两…