介绍

  • 在多线程操作中volatile关键字可以保证共享变量的内存可见性, 但是并不能保证操作的原子性, 这时候就需要用到锁, synchronized同步锁是java关键字, 是内置的语言实现.
  • synchronized加锁和线程结束或异常锁的释放过程由JVM进行控制
  • synchronized关键字可以使用在方法和同步代码块中, 不同的使用方式, 锁的结果是不同的
  • 重量级锁 + 可重入

synchronized底层原理

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
package com.liuzhihang.tool.java;

/**
* @author liuzhihang
* @date 2018/06/11 16:05
*/
public class SynchronizedTest {

private int i;

private int j;

public void syncTest1() {

synchronized (this) {
i++;
}
}

public synchronized void syncTest2() {

j++;
}


}

2.使用 javap -v SynchronizedTest.class 查看代码的对应字节码如下:

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
$ javap -v SynchronizedTest.class
Classfile /C:/Users/liuzhihang/Desktop/SynchronizedTest.class
Last modified 2018-7-10; size 518 bytes
MD5 checksum ba48def77b226e7b9ac28121ec423c16
Compiled from "SynchronizedTest.java"
public class com.liuzhihang.tool.java.SynchronizedTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
// 常量池省略
{
// 构造方法省略

public void syncTest1();
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 i:I
9: iconst_1
10: iadd
11: putfield #2 // Field i: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:
// 省略代码

public synchronized void syncTest2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: dup
2: getfield #3 // Field j:I
5: iconst_1
6: iadd
7: putfield #3 // Field j:I
10: return
LineNumberTable:
line 22: 0
line 23: 10
}
SourceFile: "SynchronizedTest.java"

3.结论

  • 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令, 其中有两个 monitorexit 因为不能确保是正常结束还是异常结束, 所以另一个是用来确保异常结束时释放 monitor指令.
  • 同步方法时使用的是 flags中的 ACC_SYNCHRONIZED 来标识该方法为同步方法, JVM在调用该方法时便会执行相应的同步调用.
  • 每个线程都维护自己的监视器(monitor), 只要是同步调用进行相关操作时要先获得 monitor, 否则将被阻塞