synchronized 各种锁状态

偏向锁的论文中给出的,偏向锁定下的锁状态转换:
synchronized 各种锁状态_第1张图片
关于偏向锁的原始论文,可以参考:HotSpot 偏向锁,原始论文翻译
可能发生的所有状态转换:
对象可能的初始锁状态只有 可偏向锁无锁 两种。
注释:可偏向状态,使用原始论文里的表达就是 the biasable but unbiased state。又叫 匿名偏向状态,后面统一使用 可偏向状态表达。

可偏向锁 可以变成 偏向锁,也可以撤销为 无锁偏向锁 可以批量重偏向为 可偏向锁,也可以根据偏向所有者是否正在持有该锁决定撤销为 轻量级锁 还是 无锁
ps:可偏向锁撤销为无锁的情况为,需要计算锁对象的 identity hash code。

无锁 可以变成 轻量级锁,也可以直接变成 重量级锁

轻量级锁 可以解锁为 无锁,也可以发生锁争用后膨胀成 重量级锁

状态转换的细节描述:
牢记,任何时刻都有可能发生锁争用。

线程在获取轻量级锁时,判断是否是自己持有该轻量级锁,如果是,则递归锁定,如果不是则轻量级锁变重量级锁。

线程请求获取处于无锁状态的锁时,先尝试加轻量级锁。加锁成功则无锁变轻量级锁,加锁失败(说明被其它线程成功加轻量级锁)则轻量级锁变重量级锁。

线程通过加轻量级锁进入同步代码块,在退出同步代码块时,首先判断锁有没有膨胀,如果锁已经膨胀为重量级锁,则按重量级锁的方式解锁,并通知正在等待获取该锁的其他线程。否则尝试解轻量级锁,解锁成功则轻量级锁变无锁,解锁失败(说明该锁被其它线程膨胀为重量级锁),则按重量级锁的方式释放锁,并通知正在等待获取该锁的其他线程。

线程请求获取处于可偏向但未偏向的锁时,先尝试让锁偏向自己。偏向成功则可偏向锁变偏向锁,偏向失败(说明偏向了其它线程)则撤销偏向,如果偏向所有者正在持有该偏向锁,则偏向锁变轻量级锁,如果允许重偏向则偏向锁变可偏向锁,否则偏向锁变无锁。
注释:如何判断偏向所有者是否正在持有偏向锁?通过遍历偏向所有者栈中的 锁记录 来判断,如果存在指向该偏向锁的锁记录,则表示偏向所有者正在持有该锁。

线程请求获取一个已经偏向于另一个线程的偏向锁。两种情况:1. 如果偏向所有者正在持有该锁,则先把偏向锁撤销为轻量级锁,然后再膨胀为重量级锁。2. 如果偏向所有者没有持有该锁,看情况又有两种选择,一是重偏向为自己,二是先把偏向锁撤销为无锁,然后再尝试轻量级锁定。

怎么把偏向所有者正在持有的锁撤销为轻量级锁呢?
线程请求偏向锁时,如果其它线程正在持有该锁,则需要所有用户线程走到一个全局安全点,此时用户线程都被阻塞。VM 线程会遍历偏向所有者的栈,将所有与该锁关联的锁记录填充进(偏向所有者如果使用轻量级锁定,将会产生的)值。接下来,更新对象的 mark word,以指向栈上最早关联该锁的锁记录。最后,释放在安全点上被阻塞的线程。
ps:线程的所有锁记录存放在栈中一段连续的内存中。线程只要进入同步代码块就会关联一个锁记录(锁记录指向锁对象),只要退出同步代码块就会释放一个锁记录(锁记录指向锁对象的指针置 NULL)。偏向锁定时,锁记录里的 displaced mark word 字段为 NULL。

最后
我认为理解各种锁优化针对的场景至关重要。

  1. 自旋锁:
    场景:很多同步代码块的执行时间很短,锁占用的时间也很短。
    在这种场景下,线程争用锁时,自旋一小段时间比进行系统调用挂起线程代价要小得多。
    自旋时间不能过长,至少不能超过线程切换的时间。否则直接切换线程或挂起线程更划算。
    自旋次数的默认值是 10 次。JDK1.6 引入自适应自旋锁,自适应自旋锁可以根据历史情况调整自旋次数。
    注释:系统调用会导致用户态和内核态的切换。为什么系统调用比普通的函数调用更耗时?用户态和内核态切换的代价在哪?
  2. 轻量级锁:
    场景:在实际程序中,大多数锁的获取是没有争用的。
    轻量级锁的目的是,请求锁时避免直接进行系统调用加重量级锁。
  3. 偏向锁:
    场景:大多数监视器不仅是没有争用的,而且在它们的生命周期中只有一个线程进入和退出。
    偏向锁的优化粒度就比较细了,认为 CAS 原子操作的开销也很大,其优化的目的是尽可能不使用原子操作。轻量级锁虽然避免了直接加重量级锁,但每次加锁和解锁时都需要 CAS 操作。而偏向锁避免了偏向所有者加锁和解锁时的 CAS 操作。
  4. 批量重偏向
    场景:在某些情况下,将一组对象重偏向到另一个线程是有好处的,特别是当一个线程分配许多对象并对每个对象执行一个初始同步操作,而另一个线程对这些对象执行后续同步操作时。
  5. 批量撤销
    场景:某个类的对象频繁被多个线程加锁,比如多个线程共享的队列。
    此时,该类的对象会频繁发生偏向撤销,禁用该类的偏向锁定是应该的。

你可能感兴趣的:(Java)