Java并发学习笔记3 synchronized

bilibili-Java并发学习笔记3 synchronized

基于 java 1.8.0

P11_synchronized关键字原理详解

P12_透过字节码理解synchronized关键字

JVM学习笔记13 synchronized

  1. 对当前类实例上锁
package new_package.thread.p12;
public class SynchronizedTest2 {
    public synchronized void hello() {
        int i = 18;
    }

    public void world() {
        synchronized (this) {
            int i = 19;
        }
    }
}
  1. 对当前class类上锁
package new_package.thread.p12;

public class SynchronizedTest4 {
    public static synchronized void hello() {
        String sync = "static";
    }
    public void world() {
        synchronized (SynchronizedTest4.class) {
            String sync = "xxx";
        }
    }
}
  1. 对类中变量加锁
package new_package.thread.p12;

public class SynchronizedTest6 {

    Object obj = new Object();

    public void hello() {
        synchronized (obj) {
            String xxx = "Object";
        }
    }

    public void world() {
        synchronized (obj) {
            String str2 = "world";
            throw  new RuntimeException("my exception");
        }
    }
}


  1. javap -v new_package.thread.p12.SynchronizedTest2
{
  public synchronized void hello();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=1, locals=2, args_size=1
         0: bipush        18
         2: istore_1
         3: return
      LineNumberTable:
        line 6: 0
        line 7: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  this   Lnew_package/thread/p12/SynchronizedTest2;
            3       1     1     i   I

  public void world();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: bipush        19
         6: istore_2
         7: aload_1
         8: monitorexit
         9: goto          17
        12: astore_3
        13: aload_1
        14: monitorexit
        15: aload_3
        16: athrow
        17: return
      Exception table:
         from    to  target type
             4     9    12   any
            12    15    12   any
      LineNumberTable:
        line 10: 0
        line 11: 4
        line 12: 7
        line 13: 17
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      18     0  this   Lnew_package/thread/p12/SynchronizedTest2;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class new_package/thread/p12/SynchronizedTest2, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}

解析1 :

  • 对于 synchronized 关键字修饰方法来说,并没有 monitorenter 和 monitorexit 指令,而是出现了一个 ACC_SYNCHRONIZED 标识
  • JVM 使用 ACC_SYNCHRONIZED 标识来区分一个方法是否为同步方法;如果是,执行线程会先持有方法所在对象的 Monitor 对象,然后再去执行方法体,线程执行完后(正常执行完返回,发生异常退出方法)释放 Monitor 对象
  • 线程持有 Monitor 对象期间,其他线程将无法再获取当前对象的 Monitor 对象
  1. javap -v new_package.thread.p12.SynchronizedTest4
{
  public new_package.thread.p12.SynchronizedTest4();
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lnew_package/thread/p12/SynchronizedTest4;

  public static synchronized void hello();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=1, locals=1, args_size=0
         0: ldc           #2                  // String static
         2: astore_0
         3: return
      LineNumberTable:
        line 5: 0
        line 6: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            3       1     0  sync   Ljava/lang/String;

  public void world();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: ldc           #3                  // class new_package/thread/p12/SynchronizedTest4
         2: dup
         3: astore_1
         4: monitorenter
         5: ldc           #4                  // String xxx
         7: astore_2
         8: aload_1
         9: monitorexit
        10: goto          18
        13: astore_3
        14: aload_1
        15: monitorexit
        16: aload_3
        17: athrow
        18: return
      Exception table:
         from    to  target type
             5    10    13   any
            13    16    13   any
      LineNumberTable:
        line 9: 0
        line 10: 5
        line 11: 8
        line 12: 18
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      19     0  this   Lnew_package/thread/p12/SynchronizedTest4;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 13
          locals = [ class new_package/thread/p12/SynchronizedTest4, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}

解析 2 :

  • 静态同步方法,同时使用 ACC_STATIC, ACC_SYNCHRONIZED 两个标识
  • 线程执行时,首先获取当前 Class 类对象(注意区别于实例对象)的 Monitor 对象,…
  1. javap -v new_package.thread.p12.SynchronizedTest6
{
  java.lang.Object obj;
    descriptor: Ljava/lang/Object;
    flags:

  public new_package.thread.p12.SynchronizedTest6();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #2                  // class java/lang/Object
         8: dup
         9: invokespecial #1                  // Method java/lang/Object."<init>":()V
        12: putfield      #3                  // Field obj:Ljava/lang/Object;
        15: return
      LineNumberTable:
        line 3: 0
        line 5: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  this   Lnew_package/thread/p12/SynchronizedTest6;

  public void hello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: aload_0
         1: getfield      #3                  // Field obj:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: ldc           #4                  // String Object
         9: astore_2
        10: aload_1
        11: monitorexit
        12: goto          20
        15: astore_3
        16: aload_1
        17: monitorexit
        18: aload_3
        19: athrow
        20: return
      Exception table:
         from    to  target type
             7    12    15   any
            15    18    15   any
      LineNumberTable:
        line 8: 0
        line 9: 7
        line 10: 10
        line 11: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      21     0  this   Lnew_package/thread/p12/SynchronizedTest6;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 15
          locals = [ class new_package/thread/p12/SynchronizedTest6, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

  public void world();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=4, args_size=1
         0: aload_0
         1: getfield      #3                  // Field obj:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: ldc           #5                  // String world
         9: astore_2
        10: new           #6                  // class java/lang/RuntimeException
        13: dup
        14: ldc           #7                  // String my exception
        16: invokespecial #8                  // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
        19: athrow
        20: astore_3
        21: aload_1
        22: monitorexit
        23: aload_3
        24: athrow
      Exception table:
         from    to  target type
             7    23    20   any
      LineNumberTable:
        line 14: 0
        line 15: 7
        line 16: 10
        line 17: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           10      10     2  str2   Ljava/lang/String;
            0      25     0  this   Lnew_package/thread/p12/SynchronizedTest6;
      StackMapTable: number_of_entries = 1
        frame_type = 255 /* full_frame */
          offset_delta = 20
          locals = [ class new_package/thread/p12/SynchronizedTest6, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
}

解析3 :

  • synchronized 代码块,字节码层次上是通过 monitorenter 和 monitorexit 指令来实现获取锁释放锁
  • 当线程进入到 monitorenter 指令后,线程将会持有 Monitor 对象,退出 monitorenter 指令后,线程将会释放 Monitor 对象;
  • hello 方法有两种结束方式:正常执行,异常结束,所以退出路径有两个,有两个 monitorexit 指令
  • world 方法主动抛出异常,所以没有正常执行结束流程,只有一个 monitorexit 指令

P14_自旋对于synchronized关键字的底层意义与价值分析

  • JVM 的同步是基于进入与退出监视器对象(管程对象 Monitor)来实现的,每个对象实例都会有一个 Monitor 对象,Monitor 对象会和 java 对象一同创建和销毁,Monitor 对象是由 C++ 来实现的。
  • 当多个线程同时访问同一段同步代码时,这些线程会被放到一个 EntryList 集合中(处于阻塞状态的线程也会在该列表中);当某一线程获取到对象的 Monitor 对象时,Monitor 依赖于底层操作系统的 mutex lock 来实现互斥,线程获取 mutex 成功,则会持有该 mutex ,这时其他线程就无法再获取到该 mutex 。
  • 如果线程调用了 wait 方法,那么该线程就会释放所持有的 mutex,并且该线程会进入到 WaieSet 集合(等待集合)中,等待下一次被其他线程调用 notify/notifyAll 唤醒。如果当前线程执行返回,也会释放所持有的 mutex 。
  • 同步锁在这种实现方式当中,因为 Monitor 是依赖于底层的操作系统实现,这样就存在用户态内核态之间的切换,所以会增加性能开销
  • 通过对象互斥锁的概念来保证共享数据操作的完整性。每个对象都对应一个可称为互斥锁的标记,这个标记用于保证在任何时刻,只能有一个线程访问该对象。
  • 那些处于 EntryList 和 WaitSet 中的线程均处于阻塞状态,阻塞操作是由操作系统完成的,在 Linux 系统下是通过 pthread_mutex_lock 函数实现的。线程被阻塞后便会进入到内核调度状态,这会导致系统在用户态与内核态之间来回切换,严重影响锁的性能。
  • 解决上述问题的办法便是自旋。其原理是:当发生对 Monitor 的争抢时,若 Owner(持有 Monitor 的线程) 能够在很短时间内释放掉锁,则那些正在争抢锁的线程就可以稍微等待一下(即所谓的自旋),在 Owner 线程释放锁之后,自旋的线程可能会立即获取到锁,从而避免阻塞。不过,当 Owner 线程运行时间超过临界值后,自旋线程自旋一段时间依然无法获取到锁,这时自旋线程则会停止自旋而进入到阻塞状态。所以总体思想是:先自旋,不成功再进行阻塞,尽量降低阻塞的可能性,这对那些执行时间很短的代码块来说有极大的性能提升。显然,自旋(需要消耗 CPU 性能)在多处理器(多核心)上才有意义。

P15_互斥锁属性详解与Monitor对象特性解说

互斥锁的属性:

  1. PTHREAD_MUTEX_TIMED_NP

属性缺省值普通锁 : 当一个线程加锁之后,其余请求锁的线程将会形成一个等待队列,并且在解锁后按照优先级获取到锁,这种策略可以确保资源分配的公平性。

  1. PTHREAD_MUTEX_RECURSIVE_NP

嵌套锁 : 允许一个线程对同一个锁成功获取多次,并通过 unlock 解锁。如果是不同线程请求,则在加锁线程解锁时重新进行竞争。

  1. PTHREAD_MUTEX_ERRORCHECK_NP

检错锁 : 如果一个线程请求同一个锁,则返回 EDEADLK ,否则与 PTHREAD_MUTEX_TIMED_NP 类型动作相同,这样就保证了当不允许多次加锁时不会出现最简单情况下的死锁。

  1. PTHREAD_MUTEX_ADAPTIVE_NP

适应锁 : 动作最简单的锁,仅仅等待解锁后重新竞争。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章