Java多线程复习与巩固(四)--synchronized的实现

小小的回顾

在上一篇文章的例子中有一个Counter类:

1
2
3
4
5
6
static class Counter {
private int c = 0;
public void increment() { c++; }
public void decrement() { c--; }
public int value() { return c; }
}

为了实现线程同步我们使用了synchronized关键字,而synchronized关键字有两种用法:

  1. 同步方法:

    1
    2
    3
    4
    5
    6
    static class Counter {
    private int c = 0;
    public synchronized void increment() { c++; }
    public synchronized void decrement() { c--; }
    public int value() { return c; }
    }
  2. 同步代码块:

    1
    2
    3
    4
    5
    6
    static class Counter {
    private int c = 0;
    public void increment() { synchronized(this){c++;} }
    public void decrement() { synchronized(this){c--;} }
    public int value() { return c; }
    }

反编译代码

我们把上面三段代码反编译一下,并取出incrementdecrement两个方法的反编译代码:

  1. 未加同步

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    public void increment();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
    stack=3, locals=1, args_size=1
    0: aload_0
    1: dup
    2: getfield #2 // Field c:I
    5: iconst_1
    6: iadd
    7: putfield #2 // Field c:I
    10: return
    LineNumberTable:
    line 3: 0

    public void decrement();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
    stack=3, locals=1, args_size=1
    0: aload_0
    1: dup
    2: getfield #2 // Field c:I
    5: iconst_1
    6: isub
    7: putfield #2 // Field c:I
    10: return
    LineNumberTable:
    line 4: 0
  2. 同步方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public synchronized void increment();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
    stack=3, locals=1, args_size=1
    0: aload_0
    1: dup
    2: getfield #2 // Field c:I
    5: iconst_1
    6: iadd
    7: putfield #2 // Field c:I
    10: return
    LineNumberTable:
    line 3: 0

    public synchronized void decrement();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
    stack=3, locals=1, args_size=1
    0: aload_0
    1: dup
    2: getfield #2 // Field c:I
    5: iconst_1
    6: isub
    7: putfield #2 // Field c:I
    10: return
    LineNumberTable:
    line 4: 0
  3. 同步代码块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76

    public void increment();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
    stack=3, locals=3, args_size=1
    0: aload_0
    1: dup
    2: astore_1
    3: monitorenter
    4: aload_0
    5: dup
    6: getfield #2 // Field c:I
    9: iconst_1
    10: iadd
    11: putfield #2 // Field c:I
    14: aload_1
    15: monitorexit
    16: goto 24
    19: astore_2
    20: aload_1
    21: monitorexit
    22: aload_2
    23: athrow
    24: return
    Exception table:
    from to target type
    4 16 19 any
    19 22 19 any
    LineNumberTable:
    line 3: 0
    StackMapTable: number_of_entries = 2
    frame_type = 255 /* full_frame */
    offset_delta = 19
    locals = [ class Counter, class java/lang/Object ]
    stack = [ class java/lang/Throwable ]
    frame_type = 250 /* chop */
    offset_delta = 4

    public void decrement();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
    stack=3, locals=3, args_size=1
    0: aload_0
    1: dup
    2: astore_1
    3: monitorenter
    4: aload_0
    5: dup
    6: getfield #2 // Field c:I
    9: iconst_1
    10: isub
    11: putfield #2 // Field c:I
    14: aload_1
    15: monitorexit
    16: goto 24
    19: astore_2
    20: aload_1
    21: monitorexit
    22: aload_2
    23: athrow
    24: return
    Exception table:
    from to target type
    4 16 19 any
    19 22 19 any
    LineNumberTable:
    line 4: 0
    StackMapTable: number_of_entries = 2
    frame_type = 255 /* full_frame */
    offset_delta = 19
    locals = [ class Counter, class java/lang/Object ]
    stack = [ class java/lang/Throwable ]
    frame_type = 250 /* chop */
    offset_delta = 4

Oracle官网维基百科有相关指令的介绍

通过看底层字节码,可以看出以下几点:

  1. 普通方法和synchronized方法在方法内部没有任何区别,仅仅是synchronized方法比普通方法多了一个ACC_SYNCHRONIZED标志位,该标志位表示访问该方法需要同步访问(synchronized access)
  2. synchronized同步代码块中由于要加载this引用,多了很多指令,而关键的两个指令是monitorentermonitorexit

JVM规范中的Monitor

Oracle官网提供的JVM规范中对monitorentermonitorexit有以下介绍:

monitorenter指令用于进入对象的monitor监视器,该指令的操作数是栈中的对象引用(objectref),这个对象引用必须是引用类型,不能使基本类型。每个对象都关联着一个monitor监视器,当且仅当monitor监视器有线程所有者(owner)才会被锁住,线程通过执行monitorenter指令尝试获取对象关联的monitor监视器的拥有权:

  • 如果一个对象(objectref)关联的monitor监视器的entry进入次数为0,该线程进入monitor监视器并将它的entry进入次数设置为1。该线程则是monitor的所有者。
  • 如果一个线程已经拥有对象(objectref)关联的monitor监视器,就让它再次进入该monitor监视器,并将entry进入次数加一。
  • 如果另一个线程已经拥有了对象(objectref)关联的monitor监视器,那么该线程将会阻塞,直到monitor监视器的entry进入次数为0时再次尝试获取所有权。

注意:

  • 如果引用对象为null,monitorenter指令将会抛出空指针异常。
  • monitorenter指令可以配合monitorexit指令来实现synchronized代码块。虽然它们提供了锁定语义,但在synchronized方法中并不会执行这两个指令,而是在调用synchronized方法时monitor进入,在方法返回时monitor退出。
  • Java的同步机制除了要实现monitorentermonitorexit这样的操作,还应该包括等待monitor监视器(Object.wait),通知等待monitor监视器的其他线程(Object.notify和Object.notifyAll)。JVM指令中不会提供这些操作的支持。

monitorexit用于退出对象的monitor监视器:

  • 执行monitorexit指令的线程必须是该monitor监视器的所有者。执行该指令时,该线程会将monitorentry进入次数减一。如果entry进入次数的结果为0,该线程将退出monitor不再是它的所有者。其他进入monitor的阻塞线程可以尝试获取该monitor监视器。

JVM规范文档说的非常清楚明白,synchronized关键字是由monitor监视器实现的。

monitor底层又是如何实现JVM规范中提到的要求呢,接下来我们从JVM实现源码来了解一下Monitor的具体实现。

Hotspot中Monitor的实现

Hotspot介绍可以参考Wiki

Hotspot源码可以从官网这里下载

在Java Hotspot中,每一个对象前面都有一个类指针和一个头字段。头字段中存储了一个哈希码(HashCode)标识值以及一个标志位,该标志位用于标识对象的年龄(新生代,老年代等),同时它也被用来实现轻量锁。下面这张图展示了头字段的位置以及不同对象状态下的字段值。