[讨论] Java synchronized是否有对应的字节码指令实现?

chainhou 2013-11-24
最近在看“深入java虚拟机”里面提到的class文件结构及其javac之后生成的字节码指令等。就想搞明白一下synchronized底层的实现方式。
测试类如下:
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
	static int count = 0;
	static AtomicInteger c = new AtomicInteger();
	public synchronized int getNum() {
		return count++;
	}
	public  int getNum0() {
		return count++;
	}
	public int getNWithAtomic() {
		return c.getAndIncrement();
	} 
}


用javap命令得到如下结果:
  static int count;
    flags: ACC_STATIC

  static java.util.concurrent.atomic.AtomicInteger c;
    flags: ACC_STATIC

  static {};
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: iconst_0
         1: putstatic     #12                 // Field count:I
         4: new           #14                 // class java/util/concurrent/atom
ic/AtomicInteger
         7: dup
         8: invokespecial #16                 // Method java/util/concurrent/ato
mic/AtomicInteger."<init>":()V
        11: putstatic     #19                 // Field c:Ljava/util/concurrent/a
tomic/AtomicInteger;
        14: return
      LineNumberTable:
        line 7: 0
        line 9: 4
        line 5: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

  public common.Counter();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #23                 // Method java/lang/Object."<init>
":()V
         4: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   Lcommon/Counter;

  public synchronized int getNum();
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #12                 // Field count:I
         3: dup
         4: iconst_1
         5: iadd
         6: putstatic     #12                 // Field count:I
         9: ireturn
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      10     0  this   Lcommon/Counter;

  public int getNum0();
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #12                 // Field count:I
         3: dup
         4: iconst_1
         5: iadd
         6: putstatic     #12                 // Field count:I
         9: ireturn
      LineNumberTable:
        line 14: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      10     0  this   Lcommon/Counter;

  public int getNWithAtomic();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: getstatic     #19                 // Field c:Ljava/util/concurrent/a
tomic/AtomicInteger;
         3: invokevirtual #30                 // Method java/util/concurrent/ato
mic/AtomicInteger.getAndIncrement:()I
         6: ireturn
      LineNumberTable:
        line 17: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       7     0  this   Lcommon/Counter;
}

从javap的结果看,方法getNum和getNum0的字节码指令一样,都是先获取static字段之后常量1入栈,再存入static中,返回。对于是否包含synchronized关键字,并没有相应的字节码指令对应。此处是我测试不对呢,还是确实实现都是这样,只是在执行时再判断方法声明中是否包含synchronized关键字,再加以控制(类似于载入class文件时判断其方法或类声明是否为public这样)?
另外,对于atomicInteger的指令,在方法getNWithAtomic方法中,也是分两个指令来执行,先获取static,再执行其相应的increment方法,那atomic底层是有东西保证这两个指令是以原子方式执行的吗?
runshine 2013-11-24
monitorenter
monitorexit
chainhou 2013-11-24
@runshine,对是这样的,我在代码中加了一个方法:
Object o = new Object();
	public void t() {
		synchronized(o) {
			System.out.println("aaa");
		}
	}

这个时候javap的输出:
  public void t();
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: aload_0
         1: getfield      #26                 // Field o:Ljava/lang/Object;
         4: dup
         5: astore_1
         6: monitorenter
         7: getstatic     #34                 // Field java/lang/System.out:Ljav
a/io/PrintStream;
        10: ldc           #40                 // String aaa
        12: invokevirtual #42                 // Method java/io/PrintStream.prin
tln:(Ljava/lang/String;)V
        15: aload_1
        16: monitorexit
        17: goto          23
        20: aload_1
        21: monitorexit
        22: athrow
        23: return
      Exception table:
         from    to  target type
             7    17    20   any
            20    22    20   any

包含了monitorenter和monitorexit
runshine 2013-11-24
对于AtomicInteger上你调用的getAndIncrement它调用了compareAndSet,compareAndSet调用了sun.misc.Unsafe的compareAndSwapInt方法
compareAndSwapInt是个native方法
一般现代的CPU都提供的有一个称为CAS的原子操作,compareAndSwapInt用的就是具体平台对应的实现。
具体参照这里http://baike.baidu.com/subview/18179/6777667.htm
RednaxelaFX 2013-11-24
楼主的疑惑在《Oracle JRockit: The Definitive Guide》里有详细描述。刚顺手写了篇笔记发在豆瓣了:http://book.douban.com/annotation/29492994/
chainhou 2013-11-24
谢谢R大和runshine的回答,解释的很清楚。
ZHH2009 2014-07-28
我来补充一下:

synchronized的实现分两种,一种是synchronized方法,一种是synchronized块,
两种方法稍有不同,但是核心还是一样的。

synchronized方法就像R大的笔记中提到的,
并没有生成对应的monitorenter、monitorexit字节码,
而是通过方法的access_flags字段中的ACC_SYNCHRONIZED标志来识别,
带有ACC_SYNCHRONIZED标志的方法在HotSpot中会通过method_entry_point_zerolocals_synchronized来触发,
在实际执行方法字节码之前会根据UseHeavyMonitors和UseBiasedLocking参数来确定
先用哪种锁,然后在方法返回时再做相关的解锁操作(体现在return字节码的汇编代码中)。

synchronized块有对应的monitorenter、monitorexit字节码,
monitorenter的汇编代码有一部分是跟method_entry_point_zerolocals_synchronized相同的,同样monitorexit的汇编代码有一部分也跟return的汇编代码相同。

具体的汇编代码这里有一点:
https://github.com/codefollower/OpenJDK-Research/blob/master/hotspot/my-docs/interpreter/stub/method_entry_point_zerolocals_synchronized.java
Global site tag (gtag.js) - Google Analytics