再谈 synchronized 实现(锁重入,锁膨胀)

 2023-01-17
原文作者:安定配平面 原文地址:https://juejin.cn/post/7049292731109081119

基本使用

  1. synchronized放在实例方法上,锁对象是当前的this对象
  2. synchronized放在类方法(静态方法)上,锁对象是方法区中的类对象
  3. synchronized修饰代码块,也就是synchronized(object){},锁对象是()中的对象

synchronized用来修饰方法时,是通过 ACC_SYNCHRONIZED 标识符来保持线程同步的。

synchronized用来修饰代码块时,是通过 monitorentermonitorexit 指令来完成

实现原理

  • 代码层面 , Synchronized 关键字。
  • JAVA 字节码层面 ,同步方法是 ACC_SYNCHRONIZED 修饰的方法定义,同步代码块使用 monitorentermonitorexit 两个指令实现。
  • JVM 层面 , 无锁,偏向锁,轻量级锁,重量级锁。
  • 操作系统层面 是调用了汇编指令 lock cmpxchg,通过 lock 保证后面的命令(cmpxchg)只能一个线程执行。
  • 硬件层面 ,是锁住了一个北桥的信号量。

1) CPMXCHG

  • 用于比较并交换操作数,CPU对CAS的原语支持
  • 非原子性,最早用于单核CPU

2) LOCK前缀

  • CPU保证被其修饰的指令的原子性
  • 禁止重排序
  • 缓存刷新到内存

借助什么来实现?

202301011530125121.png

1. 对象头中的Mark Word

202301011530137452.png

202301011530143043.png

偏向锁

    if (锁的标记位 == 01) {
        if (偏向标记是1){
            是偏向锁且可偏向
            boolean CAS操作结果 = CAS操作替换偏向线程的ID为当前线程
            if ( CAS操作结果 == 成功){
                当前线程获得锁
                执行同步代码块
            } else {
                CAS操作失败
                开始【偏向锁的撤销】{
                    等到全局安全点
                    var 状态 = 检查原来持有锁的线程的状态
                    if (状态 == terminated || 已经退出同步代码区)
                        原线程释放锁
                        当前线程获得锁
                    else if (状态 == runnable && 未退出同步代码区){
                        执行【偏向锁膨胀到轻量级锁】的过程{
                            原持有锁的线程栈幁分配锁记录、替换MarkWord并指向对象地址、执行同步代码块、CAS操作释放锁
                            当前线程执行轻量级锁的抢锁过程{
                                CAS自旋
                                if (自旋一定次数还没有获取锁){
                                    膨胀到重量级锁
                                }
                            }
                        }
                    }
                }
            }
        }else {
            goto line 4 执行CAS操作
        }
    }else {
        不是偏向锁
    }

轻量级锁

    if (锁的标记位 == 00) {
        是轻量级锁
        执行轻量级锁的抢占{
            当前线程的栈幁中 分配 【锁记录】,【锁记录】由两个部分构成,【displaced Markword】 和 【onwer指针】
            把锁对象的【对象头】中的【Markword】拷贝到锁记录中的【displaced Markword】中
            onwer指针 指向 该锁对象
            CAS修改锁对象的对象头,使其中的【指向线程锁记录的指针】 这一字段指向当前线程
            if (CAS操作成功){
                当前线程持有锁
            }else{
                CAS操作失败
                CAS自旋
                if (自旋超过一定次数还没有成功){
                    升级为重量级锁{
                        改变Markword
                        挂起当前线程
                    }
                }
            }
        }
        }else {
        不是轻量级锁
    }

2. Monitor监视器对象

Java中,每个对象里面隐式的存在一个叫monitor(对象监视器)的对象,Monitor监视器对象存在于Java对象的对象头Mark Word中,这个对象源码是采用C++实现的

     class ObjectMonitor {
    ...
      ObjectMonitor() {
        _header       = NULL; //markOop object header
        _count        = 0;    
        _waiters      = 0,   //Number of waiting threads
        _recursions   = 0;   //Thread reentry times
        _object       = NULL;  //Store Monitor object
        _owner        = NULL;  //Thread to get ObjectMonitor object
        _WaitSet      = NULL;  //List of threads in wait state
        _WaitSetLock  = 0 ; 
        _Responsible  = NULL ;
        _succ         = NULL ;
        _cxq          = NULL ;	// One way list
        FreeNext      = NULL ;
        _EntryList    = NULL ; //Thread waiting for lock BLOCKED
        _SpinFreq     = 0 ;   
        _SpinClock    = 0 ;
        OwnerIsThread = 0 ; 
        _previous_owner_tid = 0; //ID of the previous owning thread of the monitor
      }
    ...

流程

202301011530149494.png

感谢:Jacksgong

锁重入

  • 偏向锁的锁重入

    • 简单判断mark word中的偏向线程ID
  • 轻量级锁的锁重入

    • 再次放入锁记录, 只不过displaed markword为null
    • 通过锁记录的个数判断重入的次数
    • 在高位的是第一次的锁记录
  • 重量级锁的锁重入

    • 操作系统来实现