14 | 多线程之锁优化(下):使用乐观锁优化并行操作
14 | 多线程之锁优化(下):使用乐观锁优化并行操作
讲述:李良
时长09:05大小8.32M
什么是乐观锁
乐观锁的实现原理
1.CAS 如何实现原子操作
2. 处理器如何实现原子操作
优化 CAS 乐观锁
总结
思考题
赞 12
提建议
精选留言(31)
- 张学磊2019-06-20变量的原值为A,当线程T读取后到更新前这段时间,可能被其他线程更新为B值后又更新回A值,到当线程T进行CAS操作时感知不到这个变化,依然可以更新成功;StampdLock通过过去锁时返回一个时间戳可以解决该问题。
作者回复: 不仅回答了问题,还给出了解决方案,赞一个
52 - colin2019-06-20老师您好,cell数组里存数得是+1 -1这种操作值么? 还有,“LongAdder 在操作后的返回值只是一个近似准确的数值,但是 LongAdder 最终返回的是一个准确的数值”这句话中“操作后返回值”和“最终返回值”怎么理解?
作者回复: 假设操作后立即要获取到值,这个值可能是一个不准确的值。如果我们等待所有线程执行完成之后去获取,这个值肯定是准确的值。一般在做统计时,会经常用到这种操作,实时展现的只要求一个近似值,但最终的统计要求是准确的。
24 - gerry pang2020-05-12老师,我看源码中大量用for(;;),请问那它和while(true)之间有什么明显的性能区别吗?
作者回复: 虽然都是无限循环,但for(;;)是无条件循环,而while(true)是有条件循环,for(;;)编译后的指令非常简单,而while(true)编译后的指令包含了跳转、判断等。
共 3 条评论21 - 风轻扬2019-08-25老师,ABA的问题,CAS最终关心的是:值是否是A。那ABA的影响是什么呢?
作者回复: 我们假设一个队列来分析ABA问题,会更好理解。 如果一个队列有A\B\A三个数据,在线程1获取队列头节点数据A后,如果CAS发现数据没有变,则修改头节点A为A1,此时正好有一个线程删除了头节点A,又有另外一个线程也删除了后来成为头节点的B,此时头节点是依然是A,而此时第一个线程去修改A,这将导致实际修改的不是队列刚开始的那个节点A。
21 - crazypokerk2019-06-20LongAdder原理如下:将热点数据value被分离成多个单元的cell,每个cell独自维护内部的值,当前对象的实际值由cell[]数组中所有的cell累计合成。这样,热点就进行了有效的分离,提高了并行度,所以LongAdder虽然降低了并发竞争,但是却对实时更新的数据不友好。
作者回复: 是的
17 - 陆离2019-06-21解决ABA可以利用一个版本号进行验证,每次更新,版本号都+1,同时满足版本号与旧值相同才更新9
- QQ怪2019-06-20Longaddr还是不能理解,能否在举个简单点的例子理解吗?共 1 条评论6
- 左瞳2019-06-26根据你的测试结果,都是乐观锁最优,是不是线程变为100个或者以上,其他测试结果才会优于乐观锁?
作者回复: 通常情况下,乐观锁的性能是要优于悲观锁,跟线程数量没有太大关系
4 - WL2019-06-20请教老师两个问题: 1. 文章中的这句话我不太理解, "我们可以发现,LongAdder 在操作后的返回值只是一个近似准确的数值,但是 LongAdder 最终返回的是一个准确的数值". 这么判断的依据是value的计算公式吗, 为什么value的计算公式可以保证最终返回的准确性, 公式中base和数组中各个槽的权重不一样, 为什么base有这么大的权重呢? 2. 单核CPU是靠什么机制保证其他线程和进程都看到的缓存中的内容而不是内存中的内容呢?展开3
- 码农Kevin亮2019-08-06这里想反馈一下,每个小节都讲得太绕了,老师可否直接点题,我越看越困惑: 1,关于“什么是乐观锁”。乐观锁=CAS?CAS不是属于无锁嘛,所以乐观锁=无锁? 2,关于“CAS的实现原理”。CAS是通过锁缓存来实现的,是吗?而synchronized是锁总线,是吗?
作者回复: 1、乐观锁是一种概念,通过版本号来实现锁是一种乐观锁,而CAS是一种乐观锁的具体实现; 2、CAS是通过底层CPU缓存锁定实现的,这里的总线锁没有涉及到synchronized,只是之前老的CPU是根据总线锁来实现的,由于更新换代,目前使用的是性能更好的缓存锁。 建议多阅读几次文章。
2 - 文灏2019-07-03LongAdder 在操作后的返回值只是一个近似准确的数值, 但是 LongAdder 最终返回的是一个准确的数值. 那什么时候才能知道LongAdder现在返回的值是正确的了呢?
作者回复: 例如,我们在做销量统计的时候,用到LongAdder 统计销量,我们只需要保证最终写入的销量,在以后查询是是准确的。具体的时间也许是毫秒之后能查到,也许是分钟之后,但我们只需要保证在写入之后,能最终统计之前写入的销量。
2 - 晓杰2019-06-21ABA问题指的是假设现在有一个变量count的值为A,线程T1在未读取count值之前,线程T2把count值改为B,线程T3又把count值改为A,当线程T1读取count发现值为A,虽然值还是A,但是已经被其他线程改变过。 数值的原子递增可以不关注ABA问题,但是如果是原子化的更新对象,就需要关注ABA问题,因为有可能属性值发生了改变共 1 条评论2
- Bumblebee2022-05-22今日收获 LongAdder 的原理就是降低操作共享变量的并发数,也就是将对单一共享变量的操作压力分散到多个变量值上,将竞争的每个写线程的 value 值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的 value 值进行 CAS 操作,最后在读取值的时候会将原子操作的共享变量与各个分散在数组的 value 值相加,返回一个近似准确的数值。展开1
- 考休2019-11-08ABA的问题使用自增版本号或者时间戳就可以解决1
- 寻2019-06-27很有帮助,系统性的重新审视学习各个锁,顺带将老师的测试代码用JMH测试框架、面向对象化重构了下。 https://github.com/seasonsolt/lockTest,有助于自己进一步深度学习研究。
作者回复: 赞
1 - z.l2019-06-23cas方法的三个参数是如何和cpu的缓存锁定机制联系到一起的呢?感觉没有理解,还请老师解答。
作者回复: 原理就是,当某个处理器对缓存中的共享变量进行了操作,就会通知其它处理器放弃对存储该共享资源或者重新读取该共享资源。
共 2 条评论1 - slowChef2019-06-21如果从这个图看,LongAdder在几乎所有场景都远优于其他锁呀,是不是有问题呢?
作者回复: 乐观锁的性能要优于悲观锁,这个没问题。但乐观锁并不是适合所有场景,所以很多时候还是需要使用到悲观锁。
1 - Loubobooo2019-06-20一个变量V,如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其他线程修改过了吗?如果在这段期间它的值曾经被改成了B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。1
- 杯莫停2022-07-06ABA就是某个变量被修改后又被改回来,此时正在CAS的线程是分辨不出这个变量是否被修改过。这个时候应该给数据加个版本号。
- perfect2021-08-19老师你的请求量1000在代码中指的是int maxValue = 1000吗??我测出来的结果跟你完全不一样,都是StampedLock锁最好