java 锁

1. 偏向锁

  1.1 偏向锁加锁流程

    1. 在线程栈中找到第一个空闲的lock record 指向锁对象

    2. 判断对象头的markword锁标识位是否是101 也就是处于匿名偏向状态

    3. 如果是 执行cas操作将锁对象的markword 高bit位中存储当前线程的内存地址

    4. 如果不是进行轻量级锁加锁逻辑

      4.1 构造一个无锁状态的markword存入lock record中 因为轻量级锁解锁是将锁对象的markword cas替换为lock record的Displaced Mark Word

      4.2 如果是轻量级锁重入 lock record的Displaced Mark Word设置为null

  1.2 偏向锁释放流程

    1. 只需要将锁指向的lock record释放就可以 锁对象markword中存储的threadid不变

  1.3 偏向锁的撤销

    1. 在偏向锁获取锁的时候发生竞争,会升级位轻量级锁 这一操作是在safe point中进行的

    2. 在vm线程队列中提交一个偏向锁撤销任务

      2.1 查看偏向的线程是否存活 如果不存活则直接将markword改为无锁状态或匿名偏向锁状态

      2.2 如果存活 通过遍历线程栈中的lock record是否指向锁对象判断是否在同步代码块中执行,

      2.3 如果在执行同步代码块中,升级为轻量级锁

        将偏向锁关联的所有lock record的Displaced Mark Word设置为null 再将最高的lock record的Displaced Mark Word设置为无锁状态 锁对象指向这个lock record

        然后修改锁对象的markword为轻量级锁状态

  1.4 偏向锁升级为轻量级锁

    1. 抢锁线程往线程栈中找到一个空闲的lock record 锁字段指向锁对象 然后更改锁状态 发现当前是偏向锁状态并且偏向的不是当前线程 这时提交偏向锁的撤销任务到vm线程中

2. 轻量级锁

  在多个线程交替执行代码块的时候 尽量避免重量级锁的使用

  1. 加锁流程

    如果当前的锁对象markword值是无锁状态

    1.1 向当前线程栈中添加一条锁记录,lock record的锁引用保存锁对象的内存地址

    1.2 将无锁状态的markword保存到lock record的displaced_header字段

    1.3 通过cas操作将锁对象的markword 修改为持有轻量级锁的状态 并更新为指向锁记录的指针 如果cas成功则获取轻量级锁成功

 

    轻量级锁重入的区别是在cas操作将锁对象的markword值改为轻量级锁状态的时候 发现已经是轻量级锁状态

    1.4 如果是锁重入则lock record 的Displaced Mark Word字段为null

  2. 释放锁流程

    1.1 jvm在当前线程栈中找到最后一条指向锁对象的lock record 将这条锁记录的锁字段设置为null 

    1.2 然后查看这条lock record的displaced markword是否为null 如果为null则代表是重入的记录 释放这个lock record

    如果不为null则需要通过cas操作将displaced markword中无锁状态值设置到锁对象的markword中 cas成功代表释放锁成功 失败代表现在是重量级锁或者正在膨胀中

3. 重量级锁

   1. 轻量级锁膨胀为重量级锁

    重量级锁通过monitor管程实现 依赖底层操作系统的mutex lock实现需要额外的用户态到内核态的开销

    1.1 调用omAlloc获取一个可用的objectmonitor 先从私有的monitor集合中获取 如果没有则从jvm共有的gFreeList中获取一批放入私有的omfreelist

    1.2 cas操作将markword修改为膨胀中状态 如果失败说明markword已经是膨胀状态 当前线程自旋等待线程膨胀完成

    如果cas成功 设置monitor的字段,将header设置为lock record的displaced mark word值 owner设置为Lock Record obj设置为锁对象

    1.3 将锁对象的markword设置为重量级锁状态, 并指向获取的monitor对象

  2. 普通获取锁

    1. markword保存的是monitor管程对象的地址和重量级锁状态

    2. 线程进入管程中自旋调用cas将owner字段设置为当前线程 如果owner为null cas成功获取锁成功返回 如果是重入 重入计数器++ 如果当前线程是之前持有轻量级锁的线程 将owner更新为当前线程 重入计数器为1

    3. 如果cas失败 会自旋尝试获取锁,如果不行 将线程封装成ObjectWaiter节点插入到cxq(单向链表)竞争队列的队首并调用park挂起

    4. 当线程退出代码块根据唤醒策略会从cxq队列或entrylist队列中获取一个线程unpark唤醒

    5. 如果线程调用了wait方法会被放入waitset队列中 被调用notify会从waitset中取出放入cxq或entrylist中去

  3. 释放锁逻辑

    1. objectmonitor 的owner设置为null

    1.1 如果是轻量级锁升级上来owner指向lock record的指针

    1.2 重入计数器不为0 则-1

    1.3 唤醒操作根据不通策略唤醒cxq或entrylist中的线程

 

https://tech.youzan.com/tag/back-end/page/5/

posted @ 2022-03-29 23:26  rudynan  阅读(112)  评论(0编辑  收藏  举报