多线程-sychronized锁膨胀

sychronized

什么是cas

  1. cas的定义:在操作系统中,CAS通常代表“Compare And Swap”,它是一种原子操作,用于解决并发访问的问题。具体地说,CAS操作会比较并交换一个内存位置的值,只有当内存位置的值与期望的值相等时,才会将新值写入该位置。如果内存位置的值与期望的值不相等,则说明这个内存位置已经被其他线程修改,此时CAS操作不会执行任何修改,并返回失败状态。

  2. cas涉及的指令:cpmxchg,他通常和自旋锁(spin lock)配合使用,这个自旋锁在os/c++中实现略有不同具体如下,看os就行

  3. linux内核下spin lock

    typedef struct spinlock {
            union {
                    atomic_t count;
            };
    } spinlock_t;
    
    
  4. 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

  1. 结论:公平与非公平锁,其区别在于抢锁之前是否排队,一旦入队,永远排队

    1. 公平锁抢锁逻辑:线程进入-》调用lock方法-》然后tryAcquire(这里具体如下 获取当前线程,判断锁的状态是否是0,判断锁是否是否被人持有-》如果没人持有看有没有排队的,若有人排队自己也去排队;如果锁被人持有了则加锁失败,自己去入队,如果自己是head,则自选取再次拿锁尝试)

    1683364731405

  2. 非公平锁抢锁逻辑:线程进入-》调用lock方法-》然后tryAcquire(这里具体行为如下:获取当前线程-》获取锁状态是否为0-》判断是否被人持有,如果被人持有就自旋等待,外一成功就拿到了锁,如果失败了,进入队列,此时会看上一个节点是不是head,如果是在自选一次尝试拿锁,还拿不到就排队去)

    1683364794024

  3. 这是别人画的图,讲述整个流程

    1683365951524

  4. 队列:刚才说到入队,队列的数据结构如下

    public class Node{
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    }
    

关于synchronized是否是公平锁

​ 1. 像上面说的,是否公平就看入队前是否抢锁,入队之后永远排队

锁的基础知识

​ 首先介绍一下锁,和内存分布

  1. 无锁:分为101 无锁可偏向,或者偏向锁,如果前52个字符不是0,则代表偏向锁,前52位是偏向线程id,如果后三位是001则代表无锁,下面是对应的几种情况

    1683510858956

    1683510904179

    1683510915818

  2. 轻量锁 00,一个64位,前62位是一个指针,看后两位即可

  3. 重新加锁和重入锁的区别

    1683511031814

偏向锁膨胀的具体过程,结合c++代码

  1. 偏向锁的获取过程,首先有一个锁对象lock,在当前线程下创建lockrecord,然后判断是否锁是自己持有,是否过期,然后拿到锁,这就是偏向锁,当第一次之后获取,比如第二次获取,还是会建立lockrecord,判断是否自己持有是否过期,如果满足就拿到锁不需要cas,所以性能最好,释放的过程就是从栈中找到对应的lockrecord释放掉

    1683511243917

  2. 轻量锁:假设t1释放了偏向锁,t2进来拿锁,此时需要先进行偏向撤销(简单来说,撤销偏向,置成无锁001)

    其内存变化如下:(看c++文件重新写)

    1683512278579

  1. 此时如果t2释放锁,t3来了,此时t3先生成无锁的markword 00,然后lockrocrd中displacedword 设置成markword,之后cas判断当前对象头和内存中对应markword是否相等,如果相等就吧对象头中的markword修改为一个指针指向lockrecord,同时把对象头中markword的后两位改成00;

    1683512814826

    1683512887818

    1683513156764

     	4.  轻量锁膨胀为重量锁:t3修改之前,t4进来完成了t3要完成的操作,t3就是加锁失败这个时候就会膨胀,涉及到 pthredmutex;
    
    1. 批量重偏向,这里涉及到连续偏向撤销20次,40次的俩个阈值问题:这里说结论:当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至t2;当一个偏向锁如果撤销次数到达40的时候就认为这个对象设计的有问题;那么JVM会把这个对象所对应的类所有的对象都撤销偏向锁;并且新实例化的对象也是不可偏向的

    2. 锁膨胀的流程图:

      1683514657692

1683599190865

大概逻辑如上面的图,这里文字版;

获取锁对象lockee->创建lockrecord->让lockrecord的obj关联lockee->获取lockee的头信息-》此时判断是否是偏向锁(这里有一些细节,比如初始化01这些看代码,看了一遍也没记住)

1.是偏向锁,获取当前线程id,看是否偏向自己,如过偏向自己就拿到锁;如果没有则看是否关闭偏向锁,如果关闭则偏向锁升级为轻量锁;如果没关闭,看是否过期或者偏向别人,无论哪种情况都重新创建一个偏向自己的mark,通过cas设置对象头,成功就获得锁,失败就升级为轻量锁;

2.不是偏向锁则升级为轻量锁

posted @ 2023-05-09 10:46  小傻孩丶儿  阅读(18)  评论(1编辑  收藏  举报