优秀问答摘自:https://ask.csdn.net/questions/1101634
两者的共同点:
-
都是用来协调多线程对共享对象、变量的访问
-
都是可重入锁
,同一线程可以多次获得同一个锁 -
都保证了可见性和互斥性
两者的不同点:
-
ReentrantLock
显示的获得、释放锁
,synchronized隐式获得释放锁
; -
ReentrantLock 可响应中断、可轮回,synchronized 是不可以响应中断的,为处理锁的不可用性提供了更高的灵活性;
-
ReentrantLock 是
API 级别
的,synchronized 是JVM 级别
的; -
ReentrantLock
可以实现公平锁
; -
ReentrantLock 通过 Condition 可以绑定多个条件;
-
底层实现不一样, synchronized 是同步阻塞,使用的是悲观并发策略,ReentrantLock 是同步非阻塞,采用的是乐观并发策略;
-
Lock 是一个接口,而 synchronized 是 Java 中的关键字
,synchronized 是内置的语言实现; -
synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁。
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while(条件判断表达式) {
condition.wait();
}
// 处理逻辑
} finally {
lock.unlock();
}
-
Lock 可以让等待锁的线程响应中断
,而 synchronized 却不行,使用 synchronized 时,等待的线程会一直等待下去,不能够响应中断。 -
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
-
Lock 可以提高多个线程进行读操作的效率,就是实现读写锁等。
雨后
作者:對你何止一句钟意
=================================================================================
ReenTrantLock可以指定是公平锁还是非公平锁,而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获锁
。
我们刚才提及到AQS中,如果线程A正处于lock状态,线程B进来时发现线程A处于lock状态,会自动进入阻塞队列,等待取锁;这时候当线程C也进来了也发现线程A处于lock状态,也会自动进入阻塞队列。那么等A释放锁后,下次加锁到底是线程B先拿到还是线程C先拿到呢?
ReentrantLock有个构造方法用于设置锁的公平性,如果我们仅仅是new了一个ReentrantLock的话,那么就是非公平锁(默认)
,就是靠自己去争取,完全的随机性。如果我们在new ReentrantLock(true) 加入 true参数时,公平锁,就会遵循先入先出的原则,保证了锁的公平性。
课间休息,又来秀一下来自咱们群里同学的搬砖工地,坐标:云南 昆明
作者:艾尼皮卡路
=====================================================================================
首先,CAS的英文单词是Compare and Swap,即是比较并替换。CAS机制中使用了3个基本操作数:内存地址V
,旧的预期值A
,待替换的新值B
。
CAS规则是
:当需要更新一个变量的值的时候,只有当变量的预期值A和内存地址V中的实际值相同
的时候,才会把内存地址V对应的值替换成B。
下面我们通过一个例子来讲解:
- 在内存地址V中存储的值是
陈哈哈
- 线程01想要把变量的值改成
侨总
,对于线程01而言,内存地址 V=‘陈哈哈’,旧的预期值 A=‘陈哈哈’,需要替换的新值 B=‘侨总’。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210708214237642.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM5MzkwNTQ1,size_16,color_FFFFFF,t_70#pic_cent
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
er)
- 在线程01要提交更新之前,另外一个
线程02抢先一步
,将内存地址V中的值更新成了V='比特币'
。
- 线程01开始提交更新的时候,按照CAS机制,首先进行A的值与内存地址V中的值进行比较( A=‘陈哈哈’ V=‘比特币’),发现
A != V 中的实际值,提交失败
。
- 线程01未获取锁后进行
重试
,重新获取内存地址V的当前值(V=‘比特币’),并重新赋值想要修改的值(B=‘侨总’)。截至目前,线程01旧的预期值为A='比特币',B='侨总'
,这个重新尝试的过程被称为自旋
。
- 这一次就比较顺利了,没有其他线程改变该变量的值,所以线程01通过CAS机制,比较
旧的预期值A
与内存地址V
的值,相同(V == A),可以替换。
- 线程01进行替换,把地址V(V=‘比特币’)的值替换成B(B=‘侨总’)。
以上就是一个比较完整的CAS锁冲突的处理方式。
从思想上来看,synchronized属于悲观锁,悲观的认为程序中的并发问题十分严重,所以严防死守,只让一个线程操作该代码块。而CAS属于乐观锁,乐观地认为程序中的并发问题并不那么严重,所以让线程不断的去尝试更新,在并发问题不严重的时候性能要比synchronized快。
当然,CAS也有缺点,如ABA问题,自旋锁消耗问题、多变量共享一致性问题等
。
ABA:
问题描述:
线程t1将它的值从A变为B,再从B变为A。同时有线程t2要将值从A变为C。但CAS检查的时候会发现没有改变,但是实质上它已经发生了改变 。可能会造成数据的缺失。
解决方法:
CAS还是类似于乐观锁,同数据乐观锁的方式给它加一个版本号或者时间戳,如AtomicStampedReference
自旋消耗资源:
问题描述:
多个线程争夺同一个资源时,如果自旋一直不成功,将会一直占用CPU。
解决方法:
破坏掉for死循环,当超过一定时间或者一定次数时,return退出。JDK8新增的LongAddr,和ConcurrentHashMap类似的方法。当多个线程竞争时,将粒度变小,将一个变量拆分为多个变量,达到多个线程访问多个资源的效果,最后再调用sum把它合起来。
虽然base和cells都是volatile修饰的,但感觉这个sum操作没有加锁,可能sum的结果不是那么精确。
多变量共享一致性问题:
解决方法: CAS操作是针对一个变量的,如果对多个变量操作,
-
可以加锁来解决。
-
封装成对象类解决。
ABA
:如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。
举个例子1:
例一:你和女神一起喝茶,女神喝了一半去厕所了,你猥琐的喝了她剩下的半杯,然后又从你杯子里倒了半杯给她,女神回来后也不知道是否被人喝过。
如果觉得例子1太猥琐的话,请看例子2:
例二:
今天上午10:30:00:
我银行卡有一万块钱,今天我来ATM机取5000块出来买比特币,但由于ATM机硬件问题,导致取款操作同时提交了两遍,后台开启了两个线程(线程1、线程2
),两个线程都是获取当前值10000元,要更新成5000元;理想情况下,应该一个线程更新成功,一个线程更新失败,我的存款只扣除一次,也就是余额应为5000元 。
好巧不巧,也是
今天上午10:30:00:
侨总上次买币欠我5000块,经过我再三催债,表示再不还钱就把你买币的事儿告诉你媳妇!也正巧这个点儿,侨总给我转了5000元到卡里(线程3
)。没想到这再正常不过的事儿,缺被ABA问题坑了!我可忍不了!
事情是这样的: