在Java中,我们最常用的同步手段就是synchronized关键字了,下面我们来分析一下它的原理。
我们来看一下下面的代码,这是一段很简单的自增代码:
public void inc(){
i++;
}
经javac编译后:
0: getstatic #2 // Field i:I
3: iconst_1
4: iadd
5: putstatic #2 // Field i:I
8: return
如果我们把它加上synchronized关键字后:
public void inc(){
synchronized (this){
i++;
}
}
编译后:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field i:I
7: iconst_1
8: iadd
9: putstatic #2 // Field i:I
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit //此处为finally语句块的monitorexit,防止代码中抛出异常,导致锁无法释放
20: aload_2
21: athrow
22: return
synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码,这两个字节码都需要一个引用类型的参数来指明要锁定和解锁的对象。
如果代码中明确制定了对象参数(就如此处的this),那就以该对象为锁定对象。如果没有明确指定(synchronized修饰方法,而不是代码块的情况),那就看synchronized关键字修饰的是实例方法还是类方法,如果是实例方法,那么锁定对象为该对象实例;否则,锁定对象为Class对象。
在执行monitorenter指令时,首先要尝试获取对象的锁,获取成功的话,锁的计数器加一;相对地,在执行monitorexit指令时锁的计数器会减一,当计数器为0时,锁就被释放。若获取对象锁失败,则该线程就会被阻塞等待,直到锁被释放为止。