synchronized关键字
写一个测试类,里面包含了同步方法,同步代码块,还有一个不加同步的
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class SyncDemo{ public synchronized void doSth1(){ System.out.println("Hello world!"); } public void doSth2(){ synchronized(SyncDemo.class){ System.out.println("Hello world!"); } } public void doSth3(){ System.out.println("Hello world!"); } }
|
javac编译后,使用javap查看编译后的字节码文件。
主要看这三个方法,其他的省略,如下所示
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
| public synchronized void doSth1(); descriptor: ()V flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello world! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 3: 0 line 5: 8
public void doSth2(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: ldc #5 // class SyncDemo 2: dup 3: astore_1 4: monitorenter 5: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #3 // String Hello world! 10: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 13: aload_1 14: monitorexit 15: goto 23 18: astore_2 19: aload_1 20: monitorexit 21: aload_2 22: athrow 23: return Exception table: from to target type 5 15 18 any 18 21 18 any LineNumberTable: line 7: 0 line 8: 5 line 9: 13 line 10: 23 StackMapTable: number_of_entries = 2 frame_type = 255 /* full_frame */ offset_delta = 18 locals = [ class SyncDemo, class java/lang/Object ] stack = [ class java/lang/Throwable ] frame_type = 250 /* chop */ offset_delta = 4
public void doSth3(); descriptor: ()V flags: (0x0001) ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello world! 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 12: 0 line 13: 8 }
|
首先看下doSth1()跟doSth()3的区别,仅仅只是在方法那里加了一个ACC_SYNCHRONIZED标志。
也就是说,对于同步方法,jvm采用ACC_SYNCHRONIZED关键字来实现同步。
JVM的解释翻译后,大概是这样子的:
方法级的同步是隐式的,在同步方法的常量池中会多了一个ACC_SYNCHRONIZED标志,当某个线程要访问这个方法时,会先检查是否存在该同步标志,如果存在,需要先获得监视器锁,然后执行,再然后释放监视器锁。如果获得不到锁,就需要等待,获取了监视器锁才可以执行下去。
值得注意的是,如果在同步方法中存在异常,在方法中又没有捕获处理,那么,在抛出该异常的时候,会先释放所占用的监视器锁。
而doSth2()就有很大的差异了。
首先没有额外的标记(ACC_SYNCHRONIZED)
其次,System.out.println(“Hello world!”);的关键代码被包围起来了,最主要的两句就是monitorenter,还有monitorexit。监视器进入,监视器退出。
每个对象都维护着一个计数器用于记录当前被锁的次数。当执行monitorenter后,该计数器会自增1,对应的线程获得锁,而执行monitorexit后,该计数器会自减1,对应的线程也会释放锁。在计数器字段为0的时候,都可以被任何线程获得锁,而当计数器字段不为0的时候,只有已经获得了锁的线程可以再次获得锁(重入锁)。