synchronized原理

蔚落 2024-03-27 14:25 162阅读 0赞

4e33ae1c68c51a0a3dbc2deb7e141702.png

一.锁升级

synchronized相对比较‘智能’,会根据锁竞争的激烈程度进行锁升级,其锁升级策略如下:

8f42c1513beb13652cfa335a3fe241e3.png

我们一一对一下四种上锁情况进行分析:

  1. 无锁:锁对象没有锁竞争时,synchronized并不会进行上锁操作,无锁状态是理论上会存在的状态,但是在实际业务场景中几乎不存在

  2. 偏向锁:偏向锁并不是真正的加锁,而是对锁对象进行加‘标签’的操作,这个标签记录哪个线程调用了这把锁,如果后续没有其他线程来调用这把锁,就一直保持偏向锁的状态,如果后面有其他线程同样竞争这把锁,那么只需要将标签中的线程与该线程进行对比,如果不一致,则有偏向锁转化为轻量级锁。偏向锁在本质上是延迟加锁的操作:能不加锁就不加锁,尽量避免不必要的加锁开销。

  3. 轻量级锁:当有其他线程对这把锁进行锁竞争,偏向锁的锁状态就会被解除,进入轻量级锁,在这里轻量级锁是自适应的自旋锁(通过CAS来实现):通过CAS检查并更新所资源的调用情况,如果更新成功,则加锁成功,更新失败认为锁被占用,不断进行自旋等待(并不放弃cpu),但是不断的自旋是会消耗内存,一般自旋一定的次数和时间就不再自旋了,这就是所谓的‘自适应’。

  4. 重量级锁:当锁竞争进一步加强时,轻量级锁就会转化为重量级锁,而重量级锁获取锁的过程如下:首先从用户态进入内核态,判断锁资源是否被占用,没有被占用则进行加锁操作,加锁成功后返回用户态,如果被占用该线程就进入锁的等待队列,当锁资源被释放之后,经过一些列沧海桑田(不及时),这时操作系统才考虑到被挂起的线程,这时才将该线程唤醒,进行加锁操作。

我们举生活中的一个例子对这四种锁状态进行说明:

a8ece6361d65c78b5beadce822eb4244.png

我们通过代码对四种锁状态进行解释:

  1. import org.openjdk.jol.info.ClassLayout;
  2. public class Demo_404_Layout {
  3. // 定义一些变量
  4. private int count;
  5. private long count1 = 200;
  6. private String hello = "";
  7. // 定义一个对象变量
  8. private TestLayout test001 = new TestLayout();
  9. public static void main(String[] args) throws InterruptedException {
  10. // 创建一个对象的实例
  11. Object obj = new Object();
  12. // 打印实例布局
  13. System.out.println("=== 任意Object对象布局,起初为无锁状态");
  14. System.out.println(ClassLayout.parseInstance(obj).toPrintable());
  15. synchronized (obj) {
  16. System.out.println("==== 第一层synchronized加锁后");
  17. System.out.println(ClassLayout.parseInstance(obj).toPrintable());
  18. System.out.println("=== 延时4S开启偏向锁");
  19. // 延时4S开启偏向锁
  20. Thread.sleep(5000);
  21. // 创建本类的实例
  22. Demo_404_Layout monitor = new Demo_404_Layout();
  23. // 打印实例布局,注意查看锁状态为偏向锁
  24. System.out.println("=== 打印实例布局,注意查看锁状态为偏向锁");
  25. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  26. System.out.println("==== synchronized加锁");
  27. // 加锁后观察加锁信息
  28. synchronized (monitor) {
  29. System.out.println("==== 第一层synchronized加锁后");
  30. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  31. // 锁重入,查看锁信息
  32. synchronized (monitor) {
  33. System.out.println("==== 第二层synchronized加锁后,锁重入");
  34. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  35. }
  36. // 释放里层的锁
  37. System.out.println("==== 释放内层锁后");
  38. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  39. }
  40. // 释放所有锁之后
  41. System.out.println("==== 释放 所有锁");
  42. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  43. System.out.println("==== 多个线程参与锁竞争,观察锁状态");
  44. Thread thread1 = new Thread(() -> {
  45. synchronized (monitor) {
  46. System.out.println("=== 在线程A 中获取锁,参与锁竞争,当前只有线程A 竞争锁,轻度锁竞争");
  47. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  48. }
  49. });
  50. thread1.start();
  51. // 休眠一会,不与线程A 激烈竞争
  52. Thread.sleep(100);
  53. Thread thread2 = new Thread(() -> {
  54. synchronized (monitor) {
  55. System.out.println("=== 在线程B 中获取锁,与其他线程进行锁竞争");
  56. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  57. }
  58. });
  59. thread2.start();
  60. // 不休眠直接竞争锁,产生激烈竞争
  61. System.out.println("==== 不休眠直接竞争锁,产生激烈竞争");
  62. synchronized (monitor) {
  63. // 加锁后的类对象
  64. System.out.println("==== 与线程B 产生激烈的锁竞争,观察锁状态为fat lock");
  65. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  66. }
  67. // 休眠一会释放锁后
  68. Thread.sleep(100);
  69. System.out.println("==== 释放锁后");
  70. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  71. System.out.println("===========================================================================================");
  72. System.out.println("===========================================================================================");
  73. System.out.println("===========================================================================================");
  74. System.out.println("===========================================================================================");
  75. System.out.println("===========================================================================================");
  76. System.out.println("===========================================================================================");
  77. // 调用hashCode后才保存hashCode的值
  78. monitor.hashCode();
  79. // 调用hashCode后观察现象
  80. System.out.println("==== 调用hashCode后查看hashCode的值");
  81. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  82. // 强制执行垃圾回收
  83. System.gc();
  84. // 观察GC计数
  85. System.out.println("==== 调用GC后查看age的值");
  86. System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
  87. // 打印类布局,注意调用的方法不同
  88. System.out.println("==== 查看类布局");
  89. System.out.println(ClassLayout.parseClass(Demo_404_Layout.class).toPrintable());
  90. // 打印类对象布局
  91. System.out.println("==== 查看类对象布局");
  92. System.out.println(ClassLayout.parseInstance(Demo_404_Layout.class).toPrintable());
  93. }
  94. }
  95. }

cb4887119c74fb1301244fa461863fd8.png

e03845a13014e76a58718e22b1c7e24d.png

d0ac83e9c78d3361e5a8b5c822c19e51.png

720dea4b895ad2461baad35a270e6c4c.png

dd1e4d440886469d64df9c29ec99d212.png

a5187dcdf9acb8df37c67c1706a85043.png

06b18decff4ae7d1a0f09b854ca35fcf.png

aa3cdc1254c153a4b15c7ace226b9a75.png

7812f99980e288089f6f59258c4438be.png

二.锁消除

锁消除是synchronized的一种优化策略,是编译器+JVM判断锁是否能够消除,如果能够消除就直接消除了

比如以下场景:当我们对变量进行修改时,将修改的代码块加上了synchronized,但是是在单线程的情况下,不存在线程安全问题,这时JVM直接将锁消除了

需要注意的是:只有JVM百分百确定不存在线程安全问题时,才会执行锁消除

另外一种场景:虽然JVM没办法决定在哪个地方加锁(程序员决定),但是如果是在多线程情况下的读操作,就不存在线程安全问题,所以这JVM直接将锁消除了

三.锁粗化

当在一段代码中出现多次加锁和释放锁的操作,这时候JVM会对锁自动进行锁的粗化,避免了锁的多次获取和释放

21b223d6cab3ce3fbc52d6753978e4ab.png

发表评论

表情:
评论列表 (有 0 条评论,162人围观)

还没有评论,来说两句吧...

相关阅读

    相关 synchronized原理

    ![4e33ae1c68c51a0a3dbc2deb7e141702.png][] 一.锁升级 synchronized相对比较‘智能’,会根据锁竞争的激烈程度进行锁升级,

    相关 Synchronize 原理

    1. 线程的状态 Thread.State 枚举,将线程划分为六中状态。 ![image-20210531180611460][] `new` 线程刚被创建,但是还没

    相关 synchronized实现原理

    一、synchronized用法 Java中的同步块用synchronized标记。 同步块在Java中是同步在某个对象上(监视器对象)。 所有同步在一个对象上的同步

    相关 并发Synchronized原理

    最近在看一些java并发方面的知识,并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能。 以下这部分转载自:[http://www.cnblogs.com/pa