| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> 【高频面试】锁与CAS详解(?建议收藏) -> 正文阅读 |
|
[Java知识库]【高频面试】锁与CAS详解(?建议收藏) |
🔥【高频面试】锁🔒与CAS详解
???
大家好,我是java厂长,今天带你们了解高频面试之Java锁🔒!???
关于作者
文章目录此篇博文对Java学习理解底层很有帮助! 一. 悲观锁与乐观锁? 乐观锁和悲观锁问题,是出现频率比较高的面试题。本文将由浅入深,逐步介绍它们的基本概念、实现方式(含实例)、适用场景,以及可能遇到的面试官追问,希望能够帮助你打动面试官。 ? 乐观锁和悲观锁是两种思想,主要是解决并发场景下的数据争夺的问题。
二、实现方式? 悲观锁的实现方式是加锁,加锁既可以是对代码块加锁(如Java的synchronized关键字),也可以是对数据加锁(如MySQL中的排它锁)。 ? 乐观锁的实现方式有两种:CAS机制和版本号机制。 1)CAS(Compare And Swap)? CAS的原理很简单,包含三个值
如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B,返回true。否则处理器不做任何操作,返回false。 实现CAS最重要的一点,就是比较和交换操作的一致性,否则就会产生歧义。 CAS操作逻辑如下:如果内存位置V的值等于预期的A值,则将该位置更新为新值B,否则不进行任何操作。许多CAS的操作是自旋的:如果操作不成功,会一直重试,直到操作成功为止。 这里引出一个新的问题,既然CAS包含了Compare和Swap两个操作,它又如何保证原子性呢?答案是:CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。 比如当前线程比较成功后,准备更新共享变量值的时候,这个共享变量值被其他线程更改了,那么CAS函数必须返回false。 要实现这个需求,java中提供了Unsafe类,它提供了三个函数,分别用来操作基本类型int和long,以及引用类型Object。 参数的意义: var1和 var2:表示这个共享变量的内存地址。这个共享变量是var1对象的一个成员属性,var2表示这个共享变量在var1类中的内存偏移量。所以通过这两个参数就可以直接在内存中修改和读取共享变量值。 var4: 表示预期原来的值。 var5: 表示期待更新的值。 并发比较低的时候用CAS比较合适,并发比较高用synchronized比较合适。 接下来以Java中的自增操作( i++ )为例,看一下悲观锁和CAS分别是如何保证线程安全的。在Java中自增操作不是原子操作,它实际上包含三个独立的操作:第一步是读取i值;第二步是加1;第三步是将新值赋值给i
通过实验,我们发现并发执行自增操作,导致计算结果的不准确。在上面的代码测试中:num1没有进行任何线程安全方面的保护,num2使用了乐观锁(CAS),num3使用了悲观锁(synchronized)。运行程序,使用2000个线程同时对num1、num2和num3进行自增操作,可以发现:num2和num3的值总是等于2000,而num1的值常常小于2000。 首先来介绍AtomicInteger。AtomicInteger是java.util.concurrent.atomic包提供的原子类,利用CPU提供的CAS操作来保证原子性;这个包里面提供了一组原子变量类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。除了AtomicInteger外,还有AtomicBoolean、AtomicLong、AtomicReference等众多原子类。 下面看一下AtomicInteger的源码,了解下它的自增操作getAndIncrement()是如何实现的(JAVA8)
2)版本号机制? 版本号机制也可以用来实现乐观锁。版本号机制的主要思想是在数据中增加一个字段version,表示该数据的版本号,每当数据被修改时,同时读取版本号version的值,若刚才读取到的version值为当前数据库中的version值相等时才更新,则版本号加1;否则重试更新操作,直到更新成功。当某个线程查询数据时,将该数据的版本号一起查出来;当该线程更新数据时,判断当前版本号与之前读取的版本号是否一致,如果一致才进行操作。 举一个简单的银行取钱的例子: 假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。
三、面试官问:乐观锁加锁吗?在面试时,曾遇到面试官如此追问。下面是我对这个问题的理解: (1)乐观锁本身是不加锁的,只是在更新数据的时候会判断一下数据是否被其他线程已经更新过了 (2)有时乐观锁可能与加锁操作两者同时使用 四、面试官问:CAS有哪些缺点?面试到这里,我可能就要恭喜你大概率是面试通过了🥰🥰🥰🥰,面试官可能已经中意你了。不过面试官准备对你发起最后的进攻:你知道CAS这种实现方式有什么缺点吗? 1)一次性只能保证一个共享变量的原子性? 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。 2)循环会耗时? 我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。 ? 在并发冲突概率大的高竞争环境下,如果CAS一直失败,会一直重试,CPU开销较大。针对这个问题的一个思路是引入退出机制,如重试次数超过一定阈值后失败退出。当然,更重要的是避免在高竞争环境下使用乐观锁。 3)存在ABA问题(重点)? 先简单解释一下什么是ABA ? 假设有两个线程——线程1和线程2,两个线程按照顺序进行以下操作: ? (1)线程1读取内存中数据为A; ? (2)线程2将该数据修改为B; ? (3)线程2将该数据修改为A; ? (4)线程1对数据进行CAS操作 ? 在第(4)步中,由于内存中数据仍然为A,因此CAS操作成功,但实际上该数据已经被线程2修改过了。这就是ABA问题。 ? 在AtomicInteger的例子中,ABA似乎没有什么危害。但是在某些场景下,ABA却会带来隐患,例如栈顶问题:一个栈的栈顶经过两次(或多次)变化又恢复了原值,但是栈可能已发生了变化。 ? 对于ABA问题,比较有效的方案是引入版本号,内存中的值每发生一次变化,版本号都+1;在进行CAS操作时,不仅比较内存中的值,也会比较版本号,只有当二者都没有变化时,CAS才能执行成功。所以JAVA中提供了AtomicStampedReference/AtomicMarkableReference来处理会发生ABA问题的场景,主要是在对象中额外再增加一个标记来标识对象是否有过变更。 ? 问题:如果内存地址V初次读取的值是A,并且在准备赋值的时候检查到它的值仍然为A,那我们就能说它的值没有被其他线程改变过了吗? ? 如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。这个漏洞称为CAS操作的“ABA”问题。Java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”(原子标记参考 ),它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚"ABA"问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。 ? 五、适用场景? 乐观锁和悲观锁并没有优劣之分,它们有各自适合的场景;下面从两个方面进行说明。 1)功能限制? 与悲观锁相比,乐观锁适用的场景受到了更多的限制,无论是CAS还是版本号机制。 ? 例如,CAS只能保证一个共享变量的原子操作,当涉及到多个变量时,CAS是无能为力的,而 synchronized则可以通过对整个代码块加锁来处理。再比如版本号机制,如果query的时候是针对表1,而update的时候是针对表2,也很难通过简单的版本号来实现乐观锁。 2)竞争激烈程度? 如果悲观锁和乐观锁都可以使用,那么选择就要考虑竞争的激烈程度: ? 1??当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源。 ? 2??当竞争激烈(出现并发冲突的概率大)时,悲观锁更有优势,因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。
希望对各位的面试有帮助,也希望小伙伴们多多支持厂长,留下你们的爱心💕和赞👍!
💗最后厂长祝大家能够拿到心仪的Offer💗
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 21:43:46- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |