synchronized(4)-轻量级锁、自旋锁、重量级锁

一、轻量级锁

轻量级锁是由偏向锁升级的来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁

轻量级锁加锁的过程

  1. 在线程进入同步代码块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Reocrd)的空间,用于存储锁对象目前的markword的拷贝,官方称之为Displaced Mark Word。这时候线程堆栈与对象头的状态如下图所示:
  2. 拷贝对象头中的markword锁记录中;
  3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的 markword 更新为指向Lock Record的指针,并将Lock Record 里的 owner指针 指向对象的mark word。如果更新成功,则执行步骤4,否则执行步骤5;
  4. 如果跟新动作成功了,那么这个线程就拥有了该对象的锁,将对象markword的锁标志位设置为 “00” ,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图所示:
  5. 如果这个更新操作失败了,则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志位的状态值变为 “10” ,markword 中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。而当前线程便尝试使用自旋锁来获取锁,自旋就是为了不让线程阻塞,而采取循环去获取锁的过程。

【轻量级锁总结】轻量级锁是jdk1.6引入的线程优化,当锁对象使用的是轻量级锁时,说明当前同步块虽然有多个线程访问,但是没有同时访问,不需要上升到操作系统级别的阻塞。

【轻量级锁加锁过程总结】首先在线程的栈帧中创建一个锁记录(Lock Record),然后将锁对象的markword复制到锁记录中,然后通过CAS尝试将markword变更为指向锁记录的指针,并将Lock Record中的owner指针指向对象的 markword。如果成功说明轻量级锁加锁成功,则修改锁标志位为 “00”,如果失败,说明有多个线程竞争,就要膨胀为重量级锁,锁标志位变为 “10”,当前线程还需要尝试通过自旋锁来获取锁。

二、自旋锁

  1. 概念:在锁膨胀之后,虚拟机为了避免线程真实地在操作系统层面挂起,虚拟机还会做最后的努力——自旋锁。
  • JVM会进行一次赌注:它会假设在不久的将来,线程可以得到这把锁。因此,虚拟机会让当前线程做几个空循环(这也是自旋的含义,也叫cpu空转),在经过若干次循环后,如果可以得到锁,那么就顺利进入临界区。如果还不能获得锁,才会真正地将线程在操作系统层面挂起,升级为重量级锁。
  • JDK1.6中,通过 -XX:+UseSpinning 开启⾃旋锁,通过 -XX:PreBlockSpin=10 设置 ⾃旋次数;
  • JDK1.7后,已去掉此参数,由jvm⾃主控制;

三、锁消除

  • 锁消除是一种更彻底的锁优化。Java虚拟机在JIT(即时编译)编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。通过锁消除,可以节省毫无意义的请求锁时间。比如,我们有可能在⼀个不可能存在并发竞争的场合使⽤Vector,⽽Vector内部使⽤了synchronized请求锁。例如如下代码:

  • 在上述代码中的Vector,由于变量 vector 只在 createStrings() 函数中使⽤,因此,它只是⼀个单纯的 局部变量。局部变量是在线程栈上分配的,属于线程私有的数据,因此不可能被其他线程访问。所以,如果虚拟机检测到这种情况,就会将这些⽆⽤的锁操作去除掉

锁消除涉及到对象分配过程中的知识点,逃逸分析所谓逃逸分析就是观察某⼀个变量是否会逃出某⼀个作⽤域。

四、重量级锁

组件说明

整体处理流程

流程详解

  1. JVM每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下,ContentionList(contention:争论,争夺)会被大量线程进行CAS访问需要一直新增等待的线程),为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList(entry:进入)中作为候选竞争线程。
  2. Owner线程会在unlock时,将 ContentionList 中的部分线程迁移到 EntryList 中,并指定 EntryList中的某个线程为 OnDeck线程(一般是最先进去的那个线程)。
  3. Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权力交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平,但是能极大的提升系统的吞吐量,在JVM中,也把这种选择性为称之为”竞争切换“。
  4. OnDeck线程获取到锁资源后会变成Owner线程,而没有得到锁资源的仍然停留在EntryList中。如果Owner线程被wait()方法阻塞,则转移到 Waiting Queue 中,直到某个时刻通过 notify或者notifyAll唤醒,会重新进去EntryList中。处于ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。
  5. Synchronized是非公平锁。Synchronized在线程进入 ContentionList 前,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能 直接抢占 OnDeck线程的锁资源

【总结】多线程竞争锁对象时,首先会有一个线程抢到锁执行同步块,其他线程会自旋获取锁,如果自旋获取不到会进入ContentionList队列,当Owner线程unlock状态时,会将ContentionList尾部的一些线程放入EntryList,并且指定其中一个为OnDeck线程,该线程可以和正在竞争锁的线程一起竞争锁,OnDeck线程会一直等到竞争到锁,成为Owner线程。在Owner线程执行过程中,如果调用了wait() 方法,则进入WaitSet队列,直到锁对象调用notify或者notifyAll,再进入EntryList队列,等待成为OnDeck线程参与竞争。

  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值