6.synchronized


Java 中的每一个对象都可以作为锁。具体表现为以下3种形式。

  1. 对于普通同步方法,锁是当前实例对象。
  2. 对于静态同步方法,锁是当前类的 Class 对象。
  3. 对于同步方法块,锁是 Synchonized 括号里配置的对象。

1. 锁的升级

1.1. 偏向锁

image
偏向的意思是,偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),这是一中乐观锁策略

加锁
只需要在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁.

锁膨胀
在退出同步块时不会释放锁。只有在第二个线程过来产生竞争的时候才会在 全局安全点 进行释放或者膨胀为轻量锁的操作。

锁撤销
偏向锁的撤销,需要等待全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的 Mark Word 要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程.

批量再偏向
偏向锁这个机制很特殊, 别的锁在执行完同步代码块后, 都会有释放锁的操作, 而偏向锁并没有直观意义上的“释放锁”操作。
那么,如果一个对象先偏向于某个线程, 执行完同步代码后, 另一个线程如何重新获得偏向锁呢,这就要依靠JVM 提供的批量再偏向(()Bulk Rebias)机制

该机制的主要工作原理如下:

  1. 引入一个概念 epoch, 其本质是一个时间戳 , 代表了偏向锁的有效性, epoch 存储在可偏向对象的 MarkWord 中。
  2. 除了对象中的 epoch, 对象所属的类 class 信息中, 也会保存一个 epoch 值
  3. 每当遇到一个全局安全点时, 如果要对 class 进行批量再偏向, 则首先对 class 中保存的 epoch 进行增加操作, 得到一个新的 epoch_new
  4. 然后扫描所有持有 class 实例对象的线程栈, 根据线程栈的信息判断出该线程是否锁定了该对象, 仅将 epoch_new 的值赋给被锁定的对象中。
  5. 退出安全点后, 当有线程需要尝试获取偏向锁时, 直接检查 class 中存储的 epoch 值是否与目标对象中存储的 epoch 值相等, 如果不相等, 则说明该对象的偏向锁已经无效了, 可以尝试对此对象重新进行偏向操作。

1.2. 轻量级锁

image
在jvm中禁用偏向锁时,如果线程取锁会自动建立轻量级锁

在jvm中使用偏向锁时,轻量锁只能由偏向锁膨胀而来

加锁

  1. 虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word
  2. 拷贝对象头中的Mark Word复制到锁记录(Lock Record)中
  3. 使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针
  4. 如果第三步更新成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁

释放锁
以一种是在推出同步代码块的时候会释放锁。释放的方式为使用原子的CAS操作来将Displaced Mark Word替换回到对象头。
如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁

锁膨胀
如果加锁失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。 如果自旋成功则依然处于轻量级状态。如果自旋失败,则升级为重量级锁。
轻量级解锁时,会使用原子的CAS操作来将Displaced Mark Word替换回到对象头,如果成功,则表示同步过程已完成。如果失败,表示有其他线程尝试过获取该锁,锁膨胀为重量锁

1.3. 重量级锁

重量级锁依赖于操作系统的互斥量(mutex) 实现

加锁
由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的markword,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对markword做了修改,两者比对发现不一致,则切换到重量锁。应该也是创建一个数据结构,里面包含了mutex,然后把markword里的指针指向该数据结构。

2. 锁与可见性(内存刷新)

当线程获取锁时,JMM 会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量
当线程释放锁时,JMM 会把该线程对应的本地内存中的共享变量刷新到主内存中。
也就是说加锁代码会看到最新的内存而不会存在缓存一致性问题和可见性问题

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值