13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
讲述:李良
时长09:42大小8.87M
Lock 锁的实现原理
锁分离优化 Lock 同步锁
1. 读写锁 ReentrantReadWriteLock
2. 读写锁再优化之 StampedLock
总结
思考题
赞 13
提建议
精选留言(60)
- Liam2019-06-18StampLock不支持重入,不支持条件变量,线程被中断时可能导致CPU暴涨
作者回复: 回答很全面
共 3 条评论65 - KingSwim2019-07-04重复是学习最好的方式——没有之一。虽然好几个Java专栏都会讲到锁的问题。但是,每次看完都是只能懂一部分。但是,每看完一个专栏就清晰一点,只有不断的重复,才能掌握好一个知识点。感觉复习同一个也会有效果(部分专栏看过 2 边,感觉有点耽误时间,专栏太多)。但是还是不如看新的专栏,因为同时还有其他知识点的收获。现在对温故而知新的”故“有了新的理解。另外,老师的 lock 是我看过专栏里面讲得最清晰的。46
- 我知道了嗯2019-06-20可重入锁是什么?另外什么场景下会使用到?
作者回复: 可重入锁是指在同一个线程在前面方法中已获取锁了,再进入该线程的其他方法获取锁,此时不会因为之前获取锁而阻塞。 平时我们很少遇到这种情况,例如在A方法中使用了对象锁,B方法中也使用了该对象锁,平时一般都是分别调用A方法和B方法,而后面由于业务需求刚好需要在A方法中调用B方法,此时就会需要锁支持可重入性。
共 2 条评论23 - -W.LI-2019-06-18老师好!读写锁那个流程图看不太明白,没有写线程的时候,判断不是当前线程在读就会进入CLF阻塞等待。 问题1:不是可以并发读的嘛?按这图读线程也要阻塞等待的意思么? 问题二:CLF阻塞队列里是读写线程公用的么?队列里,读写交替出现。那不就没法并发读了么?
作者回复: 第一个问题,这里有一个公平锁和非公平锁的情况,如果是公平锁,即使无锁竞争的情况下,也会进入阻塞队列中排队获取锁;否则,会立即CAS获取到读锁。 第二个问题,是公用的,这里同样涉及到了公平锁和非公平锁,读写线程对于程序来说都是一样的。如果是非公平锁,如果没有锁竞争的情况下CAS获取锁成功,是无需进入阻塞队列。如果是公平锁,都会进入阻塞队列。
17 - 密码1234562019-06-18为什么?因为锁不可重入?
作者回复: 是的,StampedLock不支持可重入。如果在一些需要重入的代码中使用StampedLock,会导致死锁、饿死等情况出现。
11 - 英长2019-06-19希望老师能多结合实践讲讲应用场景10
- 码农Kevin亮2020-03-18请问老师,在读写锁的场景中,我在读操作时为什么还要加锁?直接读不就可以了?如果担心数据不刷新,那在变量加volatile是不是就可以满足?请解惑
作者回复: 在某些场景中,ReentrantLock这种读写锁能保证数据的强一致性。假设我们有两个对象x,y被volatile修饰,在A线程调用写入方法,x被写入及时更新到缓存中,而y没有,此时B线程刚好读取x,y的值,此时读取的x值是被修改过的,而y值还是原来的值,即x,y存在数据不一致的可能。
共 4 条评论7 - 王圣军2019-12-27老师这里说的公平锁和非公平锁让我想起两者是获取方式不同,非公平锁是首先就CAS来获取一次,成功就拿到锁,失败就放入队列;公平锁不会有这步操作,直接放入队列
作者回复: 是的
8 - 张三丰2020-04-10获取读锁的流程图有问题吧,应该是判断写锁是否为当前线程,而不是判断读锁。
作者回复: 对的,理解很到位,发现了问题
5 - -W.LI-2019-06-18StampedLock在写多读少的时候性能会很差吧
作者回复: 是的,写多读少的性能没有优势。
5 - QQ怪2019-06-18老师这篇干货很多,看了2~3遍,大体理解了底层AQS锁原理,期待老师多多分享更多相关的文章4
- 你好旅行者2019-06-18老师我有几个问题: 1.在ReentrantLock中,state这个变量,为0的时候表示当前的锁是没有被占用的。这个时候线程应该用CAS尝试修改state变量的值对锁进行抢占才对呀,为什么在您的图里当state=0的时候还需要判断是否为当前线程呢? 2.老师提到读写锁在读多写少的情况下会使得写线程遭遇饥饿问题,那我是不是只需要将锁设置为公平锁,这样先申请写锁的线程就可以先获得锁,从而避免饥饿问题呢? 3.StampedLock中引入了一个stamp版本对版本进行控制,那么对这个stamp变量进行写入的时候是否需要使用CAS操作?如果不是,那如何保证对stamp变量的读写是线程安全的呢? 谢谢老师!展开
作者回复: 第一个问题,是老师笔误,搞错方向了,现在已更正。 第二个问题,如果读多写少的情况下,即使是公平锁,也是需要长时间等待,不是想获取时就能立即获取到锁。StampedLock如果是处于乐观读时,写锁是可以随时获取到锁。 第三个问题,StampedLock源码中存在大量compareAndSwapObject操作来保证原子性。
4 - 奋斗的小白鼠2019-11-28老师,lock锁中的线程阻塞进行的上下文切换会设计系统内核态和用户态的转换吗?啥时候会引起系统内核态和用户态转换成啊?.io流编程中会出现吗
作者回复: lock锁阻塞不会带来进程间的上下文切换,IO流存在的,在09讲中讲到了
3 - 欧星星2019-06-21sync使用的是操作系统的Mutex Lock来实现的锁,Lock是使用线程等待来实现锁的,线程也会存在用户态内核态的切换,这样理解对吗?
作者回复: 对的。进程上下文切换,是指用户态和内核态的来回切换。我们知道,如果一旦Synchronized锁资源竞争激烈,线程将会被阻塞,阻塞的线程将会从用户态调用内核态,尝试获取mutex,这个过程就是进程上下文切换。
3 - Wheat_Liu2020-08-27老师您好,RRW真的会导致写饥饿吗?您讲了获取读锁时会先判断需不需要阻塞。我看RRW的源码发现,在新来的线程尝试获取读锁的时候,会先判断阻塞队列中下一个准备获取锁的节点是否尝试获取写锁,如果下个节点尝试获取写锁,这个新来的线程是不会抢锁的2
- 天天向上2020-08-11老师好,Lock的加锁和解锁,最终会通过调用LockSupport的方法进而调用 UNSAFE.park,或者UNSAFE.park方法,我想知道UNSAFE的这些方法会有内核态和用户态之间的切换吗共 1 条评论2
- Gavin2020-07-25例子中,乐观读时,x和y不是volatile,读的是本地内存的,有可能不是最新值,这个是不是有问题呀?2
- 又双叒叕是一年啊2019-11-10RRW 加写锁 和 读锁 都需要判断低16位? 这块写锁是不是应该判读的是高16位有没有读锁,从而判断有没有冲突?
作者回复: 从源码分析,都会进行高低位的判断,获取写锁时,如果state!=0 and w==0,就可以判断了此锁r!=0,也就是判断了高16位有没有读锁了。
2 - 你好旅行者2019-06-19谢谢老师的回复!关于StampedLock,我的理解是乐观读的时候,线程把stamp的值读出来,通过与运算来判断当前是否存在写操作。这个过程是不涉及CAS操作的。可是如果有线程需要修改当前的资源,要加写锁,那么就需要使用CAS操作修改stamp的值。不知道这样理解是否准确。 此外,前排@-W.LI-同学提出的那个问题,并发读的时候也需要按照是否是公平锁进入CLH队列进行阻塞我还不是很明白,既然大家都是读操作,互相之间没有冲突,我每个线程都直接用CAS操作获取锁不就行了吗,为什么还要进队列阻塞等待呢?展开2
- Bumblebee2022-05-22今日思考 StampedLock相比于ReentrantReadWriteLock的优点之一,在读多写少的场景下,写线程可能会长时间获取不到写锁,StampedLock利用乐观读(无锁化操作)当写线程能申请锁时能及时获取锁,不会像读写锁那样可能长时间获取不到锁。 我的意思大家应该明白了吧😂(说的不对的大家多多指正)展开1