多线程-sychronized锁膨胀
sychronized
什么是cas
-
cas的定义:在操作系统中,CAS通常代表“Compare And Swap”,它是一种原子操作,用于解决并发访问的问题。具体地说,CAS操作会比较并交换一个内存位置的值,只有当内存位置的值与期望的值相等时,才会将新值写入该位置。如果内存位置的值与期望的值不相等,则说明这个内存位置已经被其他线程修改,此时CAS操作不会执行任何修改,并返回失败状态。
-
cas涉及的指令:cpmxchg,他通常和自旋锁(spin lock)配合使用,这个自旋锁在os/c++中实现略有不同具体如下,看os就行
-
linux内核下spin lock
typedef struct spinlock { union { atomic_t count; }; } spinlock_t;
-
c++中spin lock
#include <atomic> // 头文件 class SpinLock { public: SpinLock() { flag.clear(std::memory_order_release); } void lock() { while (flag.test_and_set(std::memory_order_acquire)) ; // 忙等待 } void unlock() { flag.clear(std::memory_order_release); } private: std::atomic_flag flag; };
在多线程程序中,CAS可以用来保证数据的一致性和正确性,防止出现竞态条件等问题。因此,CAS操作是许多并发编程框架和实现中常用的技术之一。
什么是公平锁,什么是公平锁,先说reentrantlock
-
结论:公平与非公平锁,其区别在于抢锁之前是否排队,一旦入队,永远排队
- 公平锁抢锁逻辑:线程进入-》调用lock方法-》然后tryAcquire(这里具体如下 获取当前线程,判断锁的状态是否是0,判断锁是否是否被人持有-》如果没人持有看有没有排队的,若有人排队自己也去排队;如果锁被人持有了则加锁失败,自己去入队,如果自己是head,则自选取再次拿锁尝试)
-
非公平锁抢锁逻辑:线程进入-》调用lock方法-》然后tryAcquire(这里具体行为如下:获取当前线程-》获取锁状态是否为0-》判断是否被人持有,如果被人持有就自旋等待,外一成功就拿到了锁,如果失败了,进入队列,此时会看上一个节点是不是head,如果是在自选一次尝试拿锁,还拿不到就排队去)
-
这是别人画的图,讲述整个流程
-
队列:刚才说到入队,队列的数据结构如下
public class Node{ volatile Node prev; volatile Node next; volatile Thread thread; }
关于synchronized是否是公平锁
1. 像上面说的,是否公平就看入队前是否抢锁,入队之后永远排队
锁的基础知识
首先介绍一下锁,和内存分布
-
无锁:分为101 无锁可偏向,或者偏向锁,如果前52个字符不是0,则代表偏向锁,前52位是偏向线程id,如果后三位是001则代表无锁,下面是对应的几种情况
-
轻量锁 00,一个64位,前62位是一个指针,看后两位即可
-
重新加锁和重入锁的区别
偏向锁膨胀的具体过程,结合c++代码
-
偏向锁的获取过程,首先有一个锁对象lock,在当前线程下创建lockrecord,然后判断是否锁是自己持有,是否过期,然后拿到锁,这就是偏向锁,当第一次之后获取,比如第二次获取,还是会建立lockrecord,判断是否自己持有是否过期,如果满足就拿到锁不需要cas,所以性能最好,释放的过程就是从栈中找到对应的lockrecord释放掉
-
轻量锁:假设t1释放了偏向锁,t2进来拿锁,此时需要先进行偏向撤销(简单来说,撤销偏向,置成无锁001)
其内存变化如下:(看c++文件重新写)
-
此时如果t2释放锁,t3来了,此时t3先生成无锁的markword 00,然后lockrocrd中displacedword 设置成markword,之后cas判断当前对象头和内存中对应markword是否相等,如果相等就吧对象头中的markword修改为一个指针指向lockrecord,同时把对象头中markword的后两位改成00;
4. 轻量锁膨胀为重量锁:t3修改之前,t4进来完成了t3要完成的操作,t3就是加锁失败这个时候就会膨胀,涉及到 pthredmutex;
-
批量重偏向,这里涉及到连续偏向撤销20次,40次的俩个阈值问题:这里说结论:当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至t2;当一个偏向锁如果撤销次数到达40的时候就认为这个对象设计的有问题;那么JVM会把这个对象所对应的类所有的对象都撤销偏向锁;并且新实例化的对象也是不可偏向的
-
锁膨胀的流程图:
-
大概逻辑如上面的图,这里文字版;
获取锁对象lockee->创建lockrecord->让lockrecord的obj关联lockee->获取lockee的头信息-》此时判断是否是偏向锁(这里有一些细节,比如初始化01这些看代码,看了一遍也没记住)
1.是偏向锁,获取当前线程id,看是否偏向自己,如过偏向自己就拿到锁;如果没有则看是否关闭偏向锁,如果关闭则偏向锁升级为轻量锁;如果没关闭,看是否过期或者偏向别人,无论哪种情况都重新创建一个偏向自己的mark,通过cas设置对象头,成功就获得锁,失败就升级为轻量锁;
2.不是偏向锁则升级为轻量锁