synchronized锁原理monitor

monitor(监视器/管程)


java对象分三部分,

  1. 对象头
  2. 数据实例
  3. 填充

对象头分为

  1. 普通对象- markword(32bit)/klass word(32bit)(指向对应的class对象)
  2. 数组对象-多一个array length(32bit)数组长度

markword的结构

  1. hashcode(25) age(4) biased_lock:0(代表是否是偏向锁) | 01 (代表加锁状态) normal状态(正常状态)
  2. thread:54(线程id) epoch:2 age(4) biased_lock:1(代表是偏向锁)|01(代表加锁状态) biased(偏向锁)
  3. ptr_to_heavyweight_monitor(30,指向monitor的指针) |10(代表加了重量级 heavyweight lock(重量级锁定)
  4. ptr_to_lock_record:30 (30位代表锁记录的地址) |00(代表加轻锁) LightWeight Lock(轻量级锁定)

monitor是操作系统提供的,内部结构

  1. waitset

  2. entrylist(等待队列)

  3. owner(monitor拥有者)

    `具体步骤1是字节码中的monitorenter操作指令 步骤4是字节码中monitorexit指令
    重量级锁的加锁流程:
    1. thread0执行synchronize代码的时候,synchronized(obj)的obj对象的markword中ptr_to_heavyweight_monitor会指向一个monitor对象,monitor的owner是thread0
    2. thread1执行到synchronized代码时,发现obj的markword指向了一个monitor并且owner有人了,这时会进入entrylist进行blocked
    3. thread2也一样
    4. thread0执行完同步代码退出synchronized,把obj markword里的数据还原比如hashcode age啥的,这些数据是存在monitor对象中的,然后唤醒entrylist的thread1和thread2的blocked线程,两个线程去抢owner
    5. 如果出现异常,jvm会自动释放锁 执行第4步

`
因为获取monitor是系统指令,需要从用户态转为内核态,出现上下文切换啥的,比较耗费资源,所以java1.6版本对synchronized进行了优化

  1. 轻量级锁/使用场景是一个对象虽然有多个线程访问,但是不出现竞争,这时使用轻量级锁来优化
    加锁流程:

    1. 创建锁记录(lock record)对象(内部存储锁定对象的mark word,还有一个对象指针记录)
    2. 让锁记录中的object reference指向锁对象,将锁对象的mark word值存入锁记录(这时锁记录记录的是hashcode age 01无锁状态,obj锁对象对象头中存储的是锁记录地址和状态00 锁添加成功
    3. 如果obj锁对象中的状态已经是00锁记录存储的是其他线程这时加锁失败
      cas替换失败了有两种情况
      1.如果其他线程已经持有了该object的轻量级锁,这时出现了竞争,进行锁膨胀
      2.如果是线程自己执行了锁重入,添加一条lock record在锁持有线程的栈帧中 指向obj锁对象 并进行cas操作(一定失败),作为重入的计数

    释放锁流程:

    1. 退出同步代码块时,如果有锁记录为null的情况表示有重入,重置锁记录表示重入计数减一
    2. 当锁记录不为null,这时使用cas操作恢复markword给obj锁对象的对象头
      两种情况:
    3. 恢复成功 解锁成功
    4. 失败说明轻量级锁进行了锁膨胀,这时需要执行重量级锁的解锁流程

锁膨胀:
轻量级锁出现了竞争(在尝试加轻量级锁的过程中,其他线程为此对象已经加上了轻量级锁),这时需要进行锁膨胀,升级为重量级锁
thread0持有着轻量级锁,thread1进来加轻量级锁,出现竞争这时thread1加锁失败进行锁膨胀,
1. thread1为obj对象申请monitor对象,并将obj锁对象的mark word中指向monitor,thread1自己进入entrylist进行blocked
2. 当thread0执行完同步代码块,解锁的时候发现锁的标识变了,mark word也不是指向栈帧中的锁记录了,这时执行重量级锁的解锁流程

自旋优化(适用于多核处理器)
重量级锁出现竞争的时候,使用自旋来优化

偏向锁(优先加偏向锁)
轻量级锁在加锁成功后,锁重入的时候会再执行一次cas的操作(虽然必定失败),这样会浪费资源
偏向锁的加锁流程:
第一次使用cas操作将线程的id存入obj对象头的markword,之后发现这个id是自己就说明没有竞争,不用重新cas操作,只要不发生竞争,这个锁就归这个线程所有
如果调用对象的hashcode,会撤销obj锁对象的偏向状态
当申请obj对象锁的线程是不偏向的那个线程的时候(这时没有竞争),会升级为轻量级锁
批量重偏向
当偏向锁撤销超过20次,重偏向给加锁的线程
批量撤销
当偏向锁撤销超过40次,整个类的所有对象都变为不可偏向的
锁消除
jit及时编译器在运行时会优化掉

原文地址:https://www.cnblogs.com/isnotnull/p/13934420.html