开头我们先来复习一下synchronized的使用方法。
第一种在实例方法上使用表示是对当前实例的加锁:
public synchronized void myMethod2(){
System.out.println("hello world");
}
第二种是用在类方法上表示对当前类的Class对象加锁:
public static synchronized void myMethod3(){
System.out.println("hello world");
}
第三种就是synchronized代码块。
下面是一个使用的synchronized 的代码块。
public class MyTest1 {
private Object obj = new Object();
public void myMethod(){
synchronized (obj) {
System.out.println();
}
}
}
我们都知道synchronized 是一个Java的关键字,很多的书中都将它称为内置锁,也叫做监视器锁。它的实现完全是有JVM来实现的。
我们对上面的MyTest1用Javap命令进行反编译
public void myMethod();
Code:
0: aload_0
1: getfield #3 // Field obj:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: invokevirtual #5 // Method java/io/PrintStream.println:()V
13: aload_1
14: monitorexit
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return
请阅读上面的代码你会防线这个编号6 :有一个monitorenter 以及两个monitorexit 每个锁关联这个一个监视器 当执行指令monitorenter表示的是当前线程获取锁,即就是加锁的操作。monitorexit 表示的是释放锁。
但是为什么这里会有两个monitorexit呢?
因为JVM在执行时为了正常执行和反正异常都能完成锁的释放。
当线程请求一个未被持有的锁(执行monitorenter),JVM将记下锁的持有者(获得Monitor对象),并且将获取锁的计数器置为1.如果同一个线程再次获取锁,计数器的值将加1(重入的实现);当执行monitorexit (释放)释放锁计数器减1,当计数器为零,退出代码块
那么synchronized关键次在修饰方法的时候又是如何实现的呢。
public class MyTest2 {
public synchronized void printHelloWorld(){
System.out.println("Hello");
}
}
我们使用javap命令编译MyTest2
javap -v MyTest2
得到编译后的结果
public synchronized void printHelloWorld();
descriptor: ()V
flags: 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
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/crown/concurrency1/MyTest2;
我们在发现我们已经找不到monitorenter跟monitorexit的存在了。
但是在标示flags: 出现了一个ACC _SYNCHRONIZED JVM使用它来区分方法是否是同步方法, JVM在执行方法时会检查方法是否有ACC_SYNCHRONIZED ,如果有执行线程将会持有方法所在的对象的Monitor对象,在方法执行期间任何线程无法再获取这个Monitor对象,当线程执行完该方法无论是否有异常,都会在方法结束时候释放掉这个Monitor对象。
修饰静态方法字节码分析
public static synchronized void printHelloWorld();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
跟非静态的区别是标示多了一个ACC_STATIC 还有就是args_size = 0 上面的args_size = 1 那么这是为什么呢。因为无论修饰代码块还是修饰方法,方法都是属于该实例的,JVM会在参数中隐式的传入this关键字。当有ACC_STATIC 的时候方法是归Class对象所有,所以参数是零。