synchronized的实现原理和应用

1、synchronized的加锁形式主要有三种:
实例对象上加锁(通过可以同步实例方法或者同步一个实例对象)
Class对象上加锁,比如同步静态方法或者同步Class对象
方法块上加锁,将锁加到方法块上。
2、jvm对synchronized的实现方式:
无论是对方法同步还是对方法块同步,jvm都是通过Monitor对象来协助完成,但是两者的具体实现不一样。
代码块上同步是使用monitorenter和monitorexit。jvm编译后,会将monitorenter插入到同步代码开始的位置,并将monitorexit插入到同步代码结束处和异常位置(为了monitor成对)
而方法同步,它是用另一种方式实现的,用的对象头来实现的。每个对象都有一个与之关联的monitor对象,当且一个monitor被持有后,它将被处于锁定状态。
3、java对象头:
64位JVM下, Mark Word是64bit大小的,存储结构如下: 
3.1、锁的升级和对比
锁一共有四种锁,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。这几个锁会随着竞争升级而升级。锁可以升级但是不能降级。
1) 偏向锁 :因为研究发现,在大多数情况下,锁很少存在竞争,而且一个锁总是会由同一线程多次获得。为了 消除一个锁在无竞争的情况的同步原语 ,从而提高性能,因此引入了偏向锁。

当一个线程请求锁对象,会先检查这个锁对象中的mark word里的线程id是否就是当前线程id(有可能当前线程上一次已经获得过这个锁,然后释放掉),如果是,就表示已经获得了锁,就不需要使用CAS操作尝试获得锁(少了一次cas操作)。如果不指向当前线程的id,则会判断偏向锁标志,如果表示没被设置成1,则可能是无锁,或者是更高级锁,则使用CAS去 竞争锁 (因为有可能这个时候还有一个线程要竞争这个锁)。如果设置成1,表示这个锁对象是偏向锁,且已经被其它的线程持有了,这个时候则尝试使用CAS将锁对象的对象头的偏向锁指向当前线程id(因为可能mark word中的threadId指向的线程已经执行完该同步块了,只是因为锁偏向,还保留上一次持有线程的线程id)。
偏向锁的撤销,偏向锁使用的是一种 只有等到有新的锁来竞争获取该偏向锁的时候,才会释放锁 。也就是在其它线程尝试竞争这个偏向锁的时候, 持有偏向锁的线程才会释放掉这个偏向锁 。偏向锁的撤销,需要等待一个全局安全点(在这个时间点上没有正在执行的字节码)。它会首先暂停拥有偏向锁的线程,然后检查这个线程是否活着,如果线程不处于获得装填,则将锁对象头设置成无锁状态;如果线程仍活着,拥有锁的栈会被执行(加锁的代码块或方法块),遍历偏向对象的锁记录,栈中的锁记录和锁对象头的Mark word要吗重新偏向于其他线程,要吗恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。
偏向锁加锁:
  • 当锁对象第一次被线程获取的时候,虚拟机把对象头中的标志位设为“01”(即偏向模式)。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中的偏向线程ID,并将是否偏向锁的状态位置置为1。
  • 如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,直接检查ThreadId是否和自身线程Id一致, 
  • 如果一致,则认为当前线程已经获取了锁,虚拟机就可以不再进行任何同步操作(例如Locking、Unlocking及对Mark Word的Update等)。
  • 当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就如轻量级那样执行。

2)轻量级锁
轻量级锁加锁:
线程进入同步代码块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),jvm会先在当前线程的栈帧中创建一个用来存储将要申请的 锁记录的空间 ,并将锁对象中当前的mark word复制一份拷贝到这个锁记录中,官方称为 Displaced Mark Word
  • 拷贝mark word的作用:为了不想在lock与unlock这种底层操作上再加同步。
  • 修改Object mark word轻量级锁指针作用:告诉其他线程,该object monitor已被占用
  • owner指向object mark word作用:在下面的运行过程中,识别哪个对象被锁住了。
这时候堆栈与对象头的状态如下
然后线程再尝试使用CAS将锁对象中的mark workd替换为指向线程栈帧中拷贝的锁记录(lock record)的指针。如果成功,当前线程获得锁,并且对象Mark Word的锁标志位(Mark Word的最后2bit)将转变为“00”。如果失败,则表示其它线程竞争到锁,则当前线程只能尝试使用自旋来获取锁(自旋是为了防止刚好别的线程释放了锁)。 如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为”10”,Mark Word中存储的就是指向重量级(互斥量)的指针。
轻量级锁解锁:
量级解锁时,会使用原子的CAS操作来将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。


因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。
锁的优缺点对比

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值