关于java的Synchronized,你可能需要知道这些(上)
public class SynMethod { private static final Object staticLockObj = new Object(); /** * 对象锁,代码级别,同一对象争用该锁,this为SynMethod实例,synchronized的锁绑定在this对象上 */ public void method1() { synchronized (this) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } } /** * 对象锁,方法级别,同一对象争用该锁,普通(非静态)方法,synchronized的锁绑定在调用该方法的对象上,与上一个写法含义一致 */ public synchronized void method2() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } /** * 对象锁,代码级别,同一类争用该锁,绑定在staticLockObj上,不同SynMethod实例,拥有同一个staticLockObj对象 */ public void method3() { synchronized (staticLockObj) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } } /** * 类锁,代码级别,同一类争用该锁 */ public void method4() { synchronized (SynMethod.class) { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } } /** * 类锁,方法级别,同一类争用该锁,synchronized的锁绑定在SynMethod.class上 */ public static synchronized void staticMethod() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + " synchronized loop " + i); } } }
下面我们来测试一下(由于同步运行结果收到线程调度等各种影响,对于无法达到同步效果的情况,需要多运行几次)
测试情况1
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method1(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t1.method1(); } }, "B"); ta.start(); tb.start(); } }
运行结果:
A synchronized loop 0 A synchronized loop 1 A synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 0 B synchronized loop 1 B synchronized loop 2 B synchronized loop 3 B synchronized loop 4
两个线程运行了同一个对象t1的同一个public方法method1,这个方法在t1对象上同步,所以实现了同步的效果
测试情况2
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method1(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t1.method2(); } }, "B"); ta.start(); tb.start(); } }
运行结果:
A synchronized loop 0 A synchronized loop 1 A synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 0 B synchronized loop 1 B synchronized loop 2 B synchronized loop 3 B synchronized loop 4
两个线程运行同一个对象t1的不同的方法method1和method2方法,但是这两个方法是使用同一个对象t1上进行同步的,所以实现同步的效果,侧面印证了这两种写法的一致性。
测试情况3:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method3(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t2.method3(); } }, "B"); ta.start(); tb.start(); } }
运行结果:
A synchronized loop 0 A synchronized loop 1 A synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 0 B synchronized loop 1 B synchronized loop 2 B synchronized loop 3 B synchronized loop 4
这次两个线程运行了不同的类对象t1和t2的同一个方法method3,这个方法是在一个静态对象上同步,这个静态变量是在这个类的所有实例上共享的,所以也是达到了同步的效果
测试情况4:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method2(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t1.method3(); } }, "B"); ta.start(); tb.start(); } }
运行结果:
A synchronized loop 0 B synchronized loop 0 A synchronized loop 1 B synchronized loop 1 A synchronized loop 2 B synchronized loop 2 B synchronized loop 3 A synchronized loop 3 B synchronized loop 4 A synchronized loop 4
这次是两个线程运行了同一个对象t1的method2和method3方法,这个方法分别在t1对象和SynMethod类的静态对象上同步,所以达到同步效果
测试情况5:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method4(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t2.method4(); } }, "B"); ta.start(); tb.start(); } }
运行结果:
A synchronized loop 0 A synchronized loop 1 A synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 0 B synchronized loop 1 B synchronized loop 2 B synchronized loop 3 B synchronized loop 4
两个线程运行了不同对象t1和t2的同一个方法method4,该方法是在SynMethod类上同步,实现了同步效果
测试情况6:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method4(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { SynMethod.staticMethod(); } }, "B"); ta.start(); tb.start(); } }
运行结果:
A synchronized loop 0 A synchronized loop 1 A synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 0 B synchronized loop 1 B synchronized loop 2 B synchronized loop 3 B synchronized loop 4
两个线程分别运行了对象t1的method4和静态方法staticMethod,这个两个方法都在SynMethod类上同步,实现了同步的效果。
测试情况7:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method4(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t2.method3(); } }, "B"); ta.start(); tb.start(); } }
运行结果:
A synchronized loop 0 B synchronized loop 0 A synchronized loop 1 B synchronized loop 1 A synchronized loop 2 B synchronized loop 2 A synchronized loop 3 A synchronized loop 4 B synchronized loop 3 B synchronized loop 4
这次两个线程运行了两个对象的method3和method4发放,这个两个方法分别在SynMethod类和SynMethod类的静态对象上同步,所以没有达到同步效果
测试情况8:
public class SynTest { public static void main(String[] args) { final SynMethod t1 = new SynMethod(); final SynMethod t2 = new SynMethod(); Thread ta = new Thread(new Runnable() { @Override public void run() { t1.method4(); } }, "A"); Thread tb = new Thread(new Runnable() { @Override public void run() { t2.method2(); } }, "B"); ta.start(); tb.start(); } }
运行结果:
A synchronized loop 0 B synchronized loop 0 A synchronized loop 1 B synchronized loop 1 A synchronized loop 2 B synchronized loop 2 A synchronized loop 3 B synchronized loop 3 A synchronized loop 4 B synchronized loop 4
这次两个线程运行了两个对象的method4和method2方法,这两个方法分别在SynMethod类和对象t2上同步,所以没有达到同步效果。
使用总结:虽然上面说的情况比较多,但是从同步对象的角度,同步的场景只用三个,一个是SynMethod实例(可以多个),SynMethod的静态对象(共享)和SynMethod类(一个),只要是在同一个对象上同步,这个对象可以是实例对象,可以是静态对象,可以是类对象,那么就可以实现同步效果,否则无法达到同步,这也与synchronized设计的初衷一致。
2.实现原理
首先我们将SynMethod编译一下(命令:javac SynMethod.java),得到.class文件SynMethod.class,再通过反编译命令(javap -c SynMethod.class)
Compiled from "SynMethod.java" public class concurrent.SynMethod { public concurrent.SynMethod(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void method1(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: iconst_0 5: istore_2 6: iload_2 7: iconst_5 8: if_icmpge 51 11: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 14: new #3 // class java/lang/StringBuilder 17: dup 18: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 21: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 24: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String; 27: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 30: ldc #8 // String synchronized loop 32: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 35: iload_2 36: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 39: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 42: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 45: iinc 2, 1 48: goto 6 51: aload_1 52: monitorexit 53: goto 61 56: astore_3 57: aload_1 58: monitorexit 59: aload_3 60: athrow 61: return Exception table: from to target type 4 53 56 any 56 59 56 any public synchronized void method2(); Code: 0: iconst_0 1: istore_1 2: iload_1 3: iconst_5 4: if_icmpge 47 7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 10: new #3 // class java/lang/StringBuilder 13: dup 14: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 17: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 20: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String; 23: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 26: ldc #8 // String synchronized loop 28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 31: iload_1 32: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 35: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 38: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 41: iinc 1, 1 44: goto 2 47: return public void method3(); Code: 0: getstatic #12 // Field staticLockObj:Ljava/lang/Object; 3: dup 4: astore_1 5: monitorenter 6: iconst_0 7: istore_2 8: iload_2 9: iconst_5 10: if_icmpge 53 13: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 16: new #3 // class java/lang/StringBuilder 19: dup 20: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 23: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 26: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String; 29: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 32: ldc #8 // String synchronized loop 34: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 37: iload_2 38: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 41: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 44: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 47: iinc 2, 1 50: goto 8 53: aload_1 54: monitorexit 55: goto 63 58: astore_3 59: aload_1 60: monitorexit 61: aload_3 62: athrow 63: return Exception table: from to target type 6 55 58 any 58 61 58 any public void method4(); Code: 0: ldc_w #13 // class concurrent/SynMethod 3: dup 4: astore_1 5: monitorenter 6: iconst_0 7: istore_2 8: iload_2 9: iconst_5 10: if_icmpge 53 13: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 16: new #3 // class java/lang/StringBuilder 19: dup 20: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 23: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 26: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String; 29: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 32: ldc #8 // String synchronized loop 34: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 37: iload_2 38: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 41: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 44: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 47: iinc 2, 1 50: goto 8 53: aload_1 54: monitorexit 55: goto 63 58: astore_3 59: aload_1 60: monitorexit 61: aload_3 62: athrow 63: return Exception table: from to target type 6 55 58 any 58 61 58 any public static synchronized void staticMethod(); Code: 0: iconst_0 1: istore_0 2: iload_0 3: iconst_5 4: if_icmpge 47 7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 10: new #3 // class java/lang/StringBuilder 13: dup 14: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V 17: invokestatic #5 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 20: invokevirtual #6 // Method java/lang/Thread.getName:()Ljava/lang/String; 23: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 26: ldc #8 // String synchronized loop 28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 31: iload_0 32: invokevirtual #9 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 35: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 38: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 41: iinc 0, 1 44: goto 2 47: return static {}; Code: 0: new #14 // class java/lang/Object 3: dup 4: invokespecial #1 // Method java/lang/Object."<init>":()V 7: putstatic #12 // Field staticLockObj:Ljava/lang/Object; 10: return }
通过反编译出来的代码可以看到,方法级别的synchronized同步使用了monitorenter和monitorexit这个同步命令,而monitorexit出现了两次,猜测是由于异常处理的需要
monitorenter和monitorexit这两个命令的解释参考JVM规范:
monitorenter :
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
这段话的大概意思为:
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
monitorexit:
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
这段话的大概意思为:
执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。