介绍
- 在多线程操作中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;
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, 否则将被阻塞