使用的两种方式

  • 同步方法
  • 同步代码块; ```java package com.crayon2f.test;

/**

  • Created by feifan.gou@gmail.com on 2021/3/18 17:34. */ public class SynchronizedTest {

    public synchronized void synchronizedMethod() {

    1. System.out.println("this is a synchronized method.");

    }

    public void synchronizedArea() {

     synchronized (SynchronizedTest.class) {
         System.out.println("this is a synchronized area.");
     }
    

    } } ```

    查看字节码

    ```java $ javap -verbose SynchronizedTest.class public com.crayon2f.test.SynchronizedTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1

      0: aload_0
      1: invokespecial #1                  // Method java/lang/Object."<init>":()V
      4: return
    

    LineNumberTable:

     line 6: 0
    

    LocalVariableTable:

     Start  Length  Slot  Name   Signature
         0       5     0  this   Lcom/crayon2f/test/SynchronizedTest;
    

    public synchronized void synchronizedMethod(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=2, locals=1, args_size=1

      0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      3: ldc           #3                  // String this is a synchronized method.
      5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      8: return
    

    LineNumberTable:

     line 10: 0
     line 11: 8
    

    LocalVariableTable:

     Start  Length  Slot  Name   Signature
         0       9     0  this   Lcom/crayon2f/test/SynchronizedTest;
    

    public void synchronizedArea(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1

      0: ldc           #5                  // class com/crayon2f/test/SynchronizedTest
      2: dup
      3: astore_1
      4: monitorenter
      5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      8: ldc           #6                  // String this is a synchronized area.
     10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     13: aload_1
     14: monitorexit
     15: goto          23
     18: astore_2
     19: aload_1
     20: monitorexit
     21: aload_2
     22: athrow
     23: return
    

```

两种方式的实现方式

同步方法

官方解释: Method-level synchronization is performed implicitly, as part of method invocation and return. A synchronized method is distinguished in the run-time constant pool’s method_info structure by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the executing thread enters a monitor, invokes the method itself, and exits the monitor whether the method invocation completes normally or abruptly. During the time the executing thread owns the monitor, no other thread may enter it. If an exception is thrown during invocation of the synchronized method and the synchronized method does not handle the exception, the monitor for the method is automatically exited before the exception is rethrown out of the synchronized method.

翻译: 方法级的同步是隐式的。同步方法的常量池中会有一个ACC_SYNCHRONIZED标志。当某个线程要访问某个方法的时候,会检查是否有ACC_SYNCHRONIZED,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。

同步代码块

使用monitorenter和monitorexit两个指令实现。

官方解释:

monitorenter ach object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows: If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor. If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count. If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

monitorexit The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref. The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

翻译: 可以把执行monitorenter指令理解为加锁,执行monitorexit理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0,当一个线程获得锁(执行monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁。

总结

同步方法通过ACC_SYNCHRONIZED关键字隐式的对方法进行加锁。当线程要执行的方法被标注上ACC_SYNCHRONIZED时,需要先获得锁才能执行该方法。 同步代码块通过monitorenter和monitorexit执行来进行加锁。当线程执行到monitorenter的时候要先获得所锁,才能执行后面的方法。当线程执行到monitorexit的时候则要释放锁。 每个对象自身维护这一个被加锁次数的计数器,当计数器数字为0时表示可以被任意线程获得锁。当计数器不为0时,只有获得锁的线程才能再次获得锁。即可重入锁

锁升级

简单理解:偏向锁 -> 轻量级锁 -> 重量级锁

偏向锁

偏向锁就是指对象头中的 mark word 存储了当前线程的 ID;

  • 获取锁过程
  1. 检查mark word中的线程 id 是不是当前线程,如果是当前线程,进入同步代码块;不是就执行步骤 2;
  2. 进行 CAS 尝试将 线程 ID 换成自己;如果成功就执行代码块,不成功就执行步骤 3;
  3. 当拥有锁的线程到达安全点之后,挂起这个线程,进行锁升级 - 步骤 4;
  4. 锁升级,原持有偏向锁的线程,创建锁记录,将锁对象头拷贝到锁记录,唤醒持有锁的线程继续执行;然后释放轻量级锁 - 步骤 5
  5. a. 对比对象头中的锁记录指针是否指向当前线程的锁记录;

b. 对比线程锁记录中的mark word 是否和对象头的 mark word 一致;如果一致就释放锁,不一致则执行步骤 6

  1. 执行到这里,表示锁升级成重量级,释放锁,然后唤醒被挂起的线程。

    锁消除

    判断持有锁的线程是否处于同步块,如果处于就把 偏向锁 的升级为 轻量级锁;

    轻量级锁

    此时锁状态为轻量级锁

  2. 当线程尝试获取锁时,会在栈中创建一个锁记录,并把锁对象的 mark word 拷贝到锁记录中;

  3. 使用 CAS 尝试将锁对象的mark word更新为当前线程锁记录的指针,如果成功,表示持有锁,执行同步块,如果失败执行步骤 3
  4. 线程就会自旋,重复步骤 2;如果达到一定次数,没有获取成功,就执行步骤 4
  5. 锁升级,将线程锁对象头修改指向 monitor 的指针,然后继续执行代码块,释放重量级锁

    自适应自旋

    jvm 会根据上一次自旋的次数动态的调整自旋的次数,如果上一次自旋的次数少,表示线程自旋获取锁的概率大,jvm 会增加相应的次数,增加获取锁的概率,反之亦然;
    借鉴大佬的图:
    锁升级过程.png

    另外

    Monitor的实现:https://www.hollischuang.com/archives/2030