0
点赞
收藏
分享

微信扫一扫

多线程 | synchronized的底层原理

探头的新芽 2024-09-06 阅读 21

目录

1.它做了什么

使用synchronized的修饰的代码块如下,那么jvm是如何编译它的,做了什么,我们可以通过 javap -v 进行反编译下;

public class MyTest1 {
    public void method1() throws InterruptedException {
        Object object = new Object();
        synchronized (object){
            System.out.println("mingJing is the most handsome");
        }
    }
    public void method2() throws InterruptedException {
        Object object = new Object();
        synchronized (object){
            System.out.println("mingJing is the most handsome");
            throw new RuntimeException();
        }
    }
    public synchronized void method3(){
        System.out.println("mingJing is the most handsome");
    }
}

编译结果如下

/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/bin/javap -v /Users/mingjing/Documents/project/java_concurrency/build/classes/java/main/com/mingjing/concurrency/MyTest1.class
Classfile /Users/mingjing/Documents/project/java_concurrency/build/classes/java/main/com/mingjing/concurrency/MyTest1.class
  Last modified 2021-7-2; size 1053 bytes
  MD5 checksum 984cf67a767e9074756355f0028e7b22
  Compiled from "MyTest1.java"
public class com.mingjing.concurrency.MyTest1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #2.#29         // java/lang/Object."<init>":()V
   #2 = Class              #30            // java/lang/Object
   #3 = Fieldref           #31.#32        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = String             #33            // mingJing is the most handsome
   #5 = Methodref          #34.#35        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #6 = Class              #36            // java/lang/RuntimeException
   #7 = Methodref          #6.#29         // java/lang/RuntimeException."<init>":()V
   #8 = Class              #37            // com/mingjing/concurrency/MyTest1
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lcom/mingjing/concurrency/MyTest1;
  #16 = Utf8               method1
  #17 = Utf8               object
  #18 = Utf8               Ljava/lang/Object;
  #19 = Utf8               StackMapTable
  #20 = Class              #37            // com/mingjing/concurrency/MyTest1
  #21 = Class              #30            // java/lang/Object
  #22 = Class              #38            // java/lang/Throwable
  #23 = Utf8               Exceptions
  #24 = Class              #39            // java/lang/InterruptedException
  #25 = Utf8               method2
  #26 = Utf8               method3
  #27 = Utf8               SourceFile
  #28 = Utf8               MyTest1.java
  #29 = NameAndType        #9:#10         // "<init>":()V
  #30 = Utf8               java/lang/Object
  #31 = Class              #40            // java/lang/System
  #32 = NameAndType        #41:#42        // out:Ljava/io/PrintStream;
  #33 = Utf8               mingJing is the most handsome
  #34 = Class              #43            // java/io/PrintStream
  #35 = NameAndType        #44:#45        // println:(Ljava/lang/String;)V
  #36 = Utf8               java/lang/RuntimeException
  #37 = Utf8               com/mingjing/concurrency/MyTest1
  #38 = Utf8               java/lang/Throwable
  #39 = Utf8               java/lang/InterruptedException
  #40 = Utf8               java/lang/System
  #41 = Utf8               out
  #42 = Utf8               Ljava/io/PrintStream;
  #43 = Utf8               java/io/PrintStream
  #44 = Utf8               println
  #45 = Utf8               (Ljava/lang/String;)V
{
  public com.mingjing.concurrency.MyTest1();
    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 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/mingjing/concurrency/MyTest1;

  public void method1() throws java.lang.InterruptedException;
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: aload_1
         9: dup
        10: astore_2
        11: monitorenter
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: ldc           #4                  // String mingJing is the most handsome
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: aload_2
        21: monitorexit
        22: goto          30
        25: astore_3
        26: aload_2
        27: monitorexit
        28: aload_3
        29: athrow
        30: return
      Exception table:
         from    to  target type
            12    22    25   any
            25    28    25   any
      LineNumberTable:
        line 11: 0
        line 12: 8
        line 13: 12
        line 14: 20
        line 15: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      31     0  this   Lcom/mingjing/concurrency/MyTest1;
            8      23     1 object   Ljava/lang/Object;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 25
          locals = [ class com/mingjing/concurrency/MyTest1, class java/lang/Object, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
    Exceptions:
      throws java.lang.InterruptedException

  public void method2() throws java.lang.InterruptedException;
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: aload_1
         9: dup
        10: astore_2
        11: monitorenter
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: ldc           #4                  // String mingJing is the most handsome
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: new           #6                  // class java/lang/RuntimeException
        23: dup
        24: invokespecial #7                  // Method java/lang/RuntimeException."<init>":()V
        27: athrow
        28: astore_3
        29: aload_2
        30: monitorexit
        31: aload_3
        32: athrow
      Exception table:
         from    to  target type
            12    31    28   any
      LineNumberTable:
        line 18: 0
        line 19: 8
        line 20: 12
        line 21: 20
        line 22: 28
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      33     0  this   Lcom/mingjing/concurrency/MyTest1;
            8      25     1 object   Ljava/lang/Object;
      StackMapTable: number_of_entries = 1
        frame_type = 255 /* full_frame */
          offset_delta = 28
          locals = [ class com/mingjing/concurrency/MyTest1, class java/lang/Object, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
    Exceptions:
      throws java.lang.InterruptedException

  public synchronized void method3();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #4                  // String mingJing is the most handsome
         5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 26: 0
        line 27: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/mingjing/concurrency/MyTest1;
}
SourceFile: "MyTest1.java"

Process finished with exit code 0

在每个方法中都会有个monitorenter和monitorexit配套使用,有时monitorexit可能会有多个。

2.什么是Monitor

JVM的同步,是基于进入与退出监视器对象(Monitor)来实现的,也即是管程;每个对象实例都会有个Monitor对象,这是JVM分配的。Monitor对象会和java对象一同创建并销毁。Monitor对象是由C++来实现的。

当多个线程同时访问一段同步代码时,这些线程会被放到一个EntryList集合中,处于堵塞状态的线程都会被放到该列表当中(wait方法可查)。接下来,当线程获取到对象的Monitor时,Monitor是依赖于底层操作系统的mutex lock来实现互斥的,线程获取mutex成功,则会持有该mutex,这时其他线程无法再获取到该mutex。

如果线程调用了wait方法,那么该线程就会释放掉所有的mutex,并且该线程会进入到WaitSet集合中,等待下一次被其他线程调用notify或者notifyAll唤醒。如果当前线程顺利执行完毕,那么它也会释放掉所持有的mutex。

总结一下:同步锁在这种实现方式当中,因为Monitor是依赖于底层的操作系统实现,线程被阻塞后便会进入到内核调度状态,这样就存在用户态与内核态之前的切换,所以会增加性能开销。通过对象互斥锁的概念来保证共享数据操作的完整性。每个对象都对应于一个可称为「互斥锁」的标记,这个标记用于保证在任何时刻,只能有一个线程访问该对象。

如何减少用户态和内核态的切换

自旋。其原理是:当发生对Monitor的争抢时,若Owner能够在很短的时间内释放掉锁,则那些正在争抢的线程就可以稍微等待下(空转)。 在Owner线程释放锁之后,争抢线程可能会立刻释放锁,从而避免了系统阻塞。不过,当Owner运行时间超过了临界值后,争抢线程自旋一段时间后依然无法获取到锁,这时争抢线程则会停止自旋进入到阻塞状态。

3.对象头和内置锁 (ObjectMonitor)

因为我在学习的过程中发现很多地方都关联到了对象头的知识点,例如 JDK 中的 synchronized 锁优化 和 JVM 中对象年龄升级等等。要深入理解这些知识的原理,了解对象头的概念很有必要,而且可以为后面分享 synchronized 原理和 JVM 知识的时候做准备。

3.1对象头

请参考这篇文章
《Java 对象的内存布局》

3.2内置锁 (ObjectMonitor)

通常所说的对象的内置锁,是对象头 Mark Word 中的重量级锁指针指向的 monitor 对象,该对象是在 HotSpot 底层 C++ 语言编写的 (openjdk 里面看),简单看一下代码:

//结构体如下
ObjectMonitor::ObjectMonitor() {  
  _header       = NULL;  
  _count       = 0;  
  _waiters      = 0,  
  _recursions   = 0;       //线程的重入次数
  _object       = NULL;  
  _owner        = NULL;    //标识拥有该monitor的线程
  _WaitSet      = NULL;    //等待线程组成的双向循环链表,_WaitSet是第一个节点
  _WaitSetLock  = 0 ;  
  _Responsible  = NULL ;  
  _succ         = NULL ;  
  _cxq          = NULL ;    //多线程竞争锁进入时的单向链表
  FreeNext      = NULL ;  
  _EntryList    = NULL ;    //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
  _SpinFreq     = 0 ;  
  _SpinClock    = 0 ;  
  OwnerIsThread = 0 ;  
}

ObjectMonitor 队列之间的关系转换可以用下图表示:
在这里插入图片描述
对象内置锁ObjectMonitor流程:
● 所有期待获得锁的线程,在锁已经被其它线程拥有的时候,这些期待获得锁的线程就进入了对象锁的entry set区域。
● 所有曾经获得过锁,但是由于其它必要条件不满足而需要wait的时候,线程就进入了对象锁的wait set区域 。
● 在wait set区域的线程获得Notify/notifyAll通知的时候,随机的一个Thread(Notify)或者是全部的Thread(NotifyALL)从对象锁的wait set区域进入了entry set中。
在这里插入图片描述
既然提到了waitSet 和EntryList(_cxq 队列后面会说),那就看一下底层的 wait 和 notify 方法,其实在java的wait(long time)方法中也提到了
在这里插入图片描述

3.3wait方法底层

wait 方法底层的实现过程如下,可以访问openJDK:http://hg.openjdk.java.net/jdk8u/hs-dev/hotspot/file/ae5624088d86/src/share/vm/runtime/synchronizer.cpp

//1.调用ObjectSynchronizer::wait方法
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
  /*省略 */
  //2.获得Object的monitor对象(即内置锁)
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
  DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
  //3.调用monitor的wait方法
  monitor->wait(millis, true, THREAD);
  /*省略*/
}
  //4.在wait方法中调用addWaiter方法
  inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
  /*省略*/
  if (_WaitSet == NULL) {
    //_WaitSet为null,就初始化_waitSet
    _WaitSet = node;
    node->_prev = node;
    node->_next = node;
  } else {
    //否则就尾插
    ObjectWaiter* head = _WaitSet ;
    ObjectWaiter* tail = head->_prev;
    assert(tail->_next == head, "invariant check");
    tail->_next = node;
    head->_prev = node;
    node->_next = head;
    node->_prev = tail;
  }
}
  //5.然后在ObjectMonitor::exit释放锁,接着 thread_ParkEvent->park  也就是wait

通过 object 获得内置锁 (objectMonitor),通过内置锁将 Thread 封装成 ojectWaiter 对象,然后 addWaiter 将它插入以_waitSet 为首结点的等待线程链表中去,最后释放锁。

3.4notify 方法的底层实现

//1.调用ObjectSynchronizer::notify方法
    void ObjectSynchronizer::notify(Handle obj, TRAPS) {
    /*省略*/
    //2.调用ObjectSynchronizer::inflate方法
    ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
}
    //3.通过inflate方法得到ObjectMonitor对象
    ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
    /*省略*/
     if (mark->has_monitor()) {
          ObjectMonitor * inf = mark->monitor() ;
          assert (inf->header()->is_neutral(), "invariant");
          assert (inf->object() == object, "invariant") ;
          assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is inva;lid");
          return inf 
      }
    /*省略*/ 
      }
    //4.调用ObjectMonitor的notify方法
    void ObjectMonitor::notify(TRAPS) {
    /*省略*/
    //5.调用DequeueWaiter方法移出_waiterSet第一个结点
    ObjectWaiter * iterator = DequeueWaiter() ;
    //6.后面省略是将上面DequeueWaiter尾插入_EntrySet的操作
    /**省略*/
  }

通过 object 获得内置锁 (objectMonitor),调用内置锁的 notify 方法,通过waitset 结点移出等待链表中的首结点,将它置于EntrySet 中去,等待获取锁。注意:notifyAll 根据 policy 不同可能移入EntryList 或者cxq 队列中,此处不详谈。

4.总结

上面所介绍的通过 synchronzied 实现同步用到了对象的内置锁 (ObjectMonitor),而在 ObjectMonitor 的函数调用中会涉及到 Mutex lock 等特权指令,那么这个时候就存在操作系统用户态和核心态的转换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,这也是为什么早期的 synchronized 效率低的原因。在 jdk1.6 之后,从 jvm 层面做了很大的优化,下一篇我们主要介绍做了哪些优化。

举报

相关推荐

0 条评论