对于多线程编程而言其实老生成谈的就是数据同步问题,接下来就会开始接触这块的东东,比较麻烦,但是也是非常重要,所以按部就班的一点点去专研它,下面开始。
数据同步引入:
这里用之前写过的银行叫号的功能做为数据同步知识的引入,具体可以查看: http://www.cnblogs.com/webor2006/p/7709647.html,先复习一下代码:
public class TicketWindowRunnable implements Runnable { private static final int MAX = 50;//最大号 private int index = 1; @Override public void run() { while (index < MAX) { System.out.println(Thread.currentThread().getName() + "当前的号码是:" + (index++)); } } }
/** * 银行 */ public class Bank { public static void main(String[] args) { TicketWindowRunnable ticketWindowRunnable = new TicketWindowRunnable(); Thread window1 = new Thread(ticketWindowRunnable, "一号窗口"); window1.start(); Thread window2 = new Thread(ticketWindowRunnable, "二号窗口"); window2.start(); Thread window3 = new Thread(ticketWindowRunnable, "三号窗口"); window3.start(); } }
不过这里为了说明同步问题对TicketWindowRunnable稍加变换一下形式:
编译运行:
柜台:一号柜台,当前的号码是:1 柜台:一号柜台,当前的号码是:4 柜台:一号柜台,当前的号码是:5 柜台:一号柜台,当前的号码是:6 柜台:一号柜台,当前的号码是:7 柜台:一号柜台,当前的号码是:8 柜台:一号柜台,当前的号码是:9 柜台:三号柜台,当前的号码是:3 柜台:二号柜台,当前的号码是:2 柜台:三号柜台,当前的号码是:11 柜台:三号柜台,当前的号码是:13 柜台:一号柜台,当前的号码是:10 柜台:三号柜台,当前的号码是:14 柜台:二号柜台,当前的号码是:12 柜台:二号柜台,当前的号码是:17 柜台:三号柜台,当前的号码是:16 柜台:一号柜台,当前的号码是:15 柜台:一号柜台,当前的号码是:20 柜台:一号柜台,当前的号码是:21 柜台:三号柜台,当前的号码是:19 柜台:二号柜台,当前的号码是:18 柜台:二号柜台,当前的号码是: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 柜台:三号柜台,当前的号码是:23 柜台:一号柜台,当前的号码是:22
这其实也就是由于线程的同步问题造成的,因为每个线程在执行过程中都有可能被切成Runnable状态,这个结果只是乱序的还无所谓,下面看这种情况:
编译运行:
其原因还是线程的数据同步,那到底这个结果是如何发生的呢?下面用图来解释一下:
这就是为什么500、501会被打印出来,这就是典型线程同步问题造成,如何解决这种总是呢?使用同步块,如下:
再编译运行:
无论执行多少次其结果都一样,而且是按顺序输出的,并不会溢出,这就是关于线程同步的一个简单引入。
结合jconsole,jstack以及汇编指令认识synchronized关键字:
在上面线程同步中使用到了"synchronized"关键字,那它到底是啥东东呢?用一个形象的现实例子:就像咱们去景点游玩得买票,有多很游客都拿着票在进门口,在进门口会有检票员进行检票,进行验票通过之后则游客一个个进入景区进行游览观光,一次只允许一个人通过,其synchronized的意义就类似于检票员,让多线程进行串行化的运行,那么问题来了:难道在synchronized代码块中多线程会变成单线程么?确实就是单线程处理的,为了线程的正确执行,所以说使用synchronized之后会影响程序的执行效率,在效率和正确性面前当然正确性更重要一些啦。
那synchronized到底是个什么东东呢?下面从几个角度来分析一下,先新建一个程序:
然后运行,接下来用三种方式来进一步理解这个关键字:
jconsole:
这时打开jconsole工具来查看一下线程情况:
jstack:
先用jps来查看一下咱们运行的测试程序的进程ID:
然后再用jstack命令来查看:
xiongweideMacBook-Pro:LableCoffee xiongwei$ jstack 70524 2017-12-16 21:21:06 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.60-b23 mixed mode): "Attach Listener" #13 daemon prio=9 os_prio=31 tid=0x00007fb70b001800 nid=0x1307 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "DestroyJavaVM" #12 prio=5 os_prio=31 tid=0x00007fb70a0d2000 nid=0x1a03 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Thread-2" #11 prio=5 os_prio=31 tid=0x00007fb709817800 nid=0x4f03 waiting for monitor entry [0x000070000cdee000] java.lang.Thread.State: BLOCKED (on object monitor) at com.javaconcurrency.synchronize.SynchronizedTest.lambda$main$0(SynchronizedTest.java:11) - waiting to lock <0x0000000795778a40> (a java.lang.Object) at com.javaconcurrency.synchronize.SynchronizedTest$$Lambda$1/1149319664.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "Thread-1" #10 prio=5 os_prio=31 tid=0x00007fb70b0e0000 nid=0x4d03 waiting for monitor entry [0x000070000cceb000] java.lang.Thread.State: BLOCKED (on object monitor) at com.javaconcurrency.synchronize.SynchronizedTest.lambda$main$0(SynchronizedTest.java:11) - waiting to lock <0x0000000795778a40> (a java.lang.Object) at com.javaconcurrency.synchronize.SynchronizedTest$$Lambda$1/1149319664.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "Thread-0" #9 prio=5 os_prio=31 tid=0x00007fb70a0d1000 nid=0x4b03 waiting on condition [0x000070000cbe8000] java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method) at com.javaconcurrency.synchronize.SynchronizedTest.lambda$main$0(SynchronizedTest.java:11) - locked <0x0000000795778a40> (a java.lang.Object) at com.javaconcurrency.synchronize.SynchronizedTest$$Lambda$1/1149319664.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "Service Thread" #8 daemon prio=9 os_prio=31 tid=0x00007fb70a83e000 nid=0x4703 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread2" #7 daemon prio=9 os_prio=31 tid=0x00007fb70a858000 nid=0x4503 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread1" #6 daemon prio=9 os_prio=31 tid=0x00007fb70a857800 nid=0x4303 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #5 daemon prio=9 os_prio=31 tid=0x00007fb70a855800 nid=0x4103 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007fb70a855000 nid=0x3f0b runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007fb709803800 nid=0x3203 in Object.wait() [0x000070000c4d3000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x00000007955870b8> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143) - locked <0x00000007955870b8> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209) "Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007fb70b028800 nid=0x3003 in Object.wait() [0x000070000c3d0000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0000000795586af8> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:157) - locked <0x0000000795586af8> (a java.lang.ref.Reference$Lock) "VM Thread" os_prio=31 tid=0x00007fb70a018000 nid=0x2e03 runnable "GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007fb70a014800 nid=0x2607 runnable "GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007fb70a808000 nid=0x2803 runnable "GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007fb70a808800 nid=0x2a03 runnable "GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007fb70a809000 nid=0x2c03 runnable "VM Periodic Task Thread" os_prio=31 tid=0x00007fb70980a000 nid=0x4903 waiting on condition JNI global references: 308
其中只要咱们的三个运行的线程,可以发现目前Thread-0已经开始休眠了,也就是拿到了同步锁开始执行程序了,而其它两个线程全部处理等待状态,通过这也可以清楚的体现到synchronized关键字的作用。
反汇编指令:
那synchronized关键字在java字节反汇编里表现又如何呢?这里可以通过java的一个命令来查看字符码对应的反汇编指令,具体使用如下:
接着用javap命令来查看反汇编指令,如下:
xiongweideMacBook-Pro:synchronize xiongwei$ javap -c TicketWindowRunnable.class Compiled from "TicketWindowRunnable.java" public class com.javaconcurrency.synchronize.TicketWindowRunnable implements java.lang.Runnable { public com.javaconcurrency.synchronize.TicketWindowRunnable(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: aload_0 5: iconst_1 6: putfield #2 // Field index:I 9: aload_0 10: new #3 // class java/lang/Object 13: dup 14: invokespecial #1 // Method java/lang/Object."<init>":()V 17: putfield #4 // Field MONITOR:Ljava/lang/Object; 20: return public void run(); Code: 0: aload_0 1: getfield #4 // Field MONITOR:Ljava/lang/Object; 4: dup 5: astore_1 6: monitorenter 7: aload_0 8: getfield #2 // Field index:I 11: sipush 500 14: if_icmplt 22 17: aload_1 18: monitorexit 19: goto 93 22: ldc2_w #6 // long 5l 25: invokestatic #8 // Method java/lang/Thread.sleep:(J)V 28: goto 36 31: astore_2 32: aload_2 33: invokevirtual #10 // Method java/lang/InterruptedException.printStackTrace:()V 36: getstatic #11 // Field java/lang/System.out:Ljava/io/PrintStream; 39: new #12 // class java/lang/StringBuilder 42: dup 43: invokespecial #13 // Method java/lang/StringBuilder."<init>":()V 46: invokestatic #14 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread; 49: invokevirtual #15 // Method java/lang/Thread.getName:()Ljava/lang/String; 52: invokevirtual #16 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 55: ldc #17 // String 当前的号码是: 57: invokevirtual #16 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 60: aload_0 61: dup 62: getfield #2 // Field index:I 65: dup_x1 66: iconst_1 67: iadd 68: putfield #2 // Field index:I 71: invokevirtual #18 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 74: invokevirtual #19 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 77: invokevirtual #20 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 80: aload_1 81: monitorexit 82: goto 90 85: astore_3 86: aload_1 87: monitorexit 88: aload_3 89: athrow 90: goto 0 93: return Exception table: from to target type 22 28 31 Class java/lang/InterruptedException 7 19 85 any 22 82 85 any 85 88 85 any }
接着对照着咱们的java源代码来分析:
而对应汇编代码中有这样一句话:
实际上也就对应咱们的同步块代码 :
接着应该程序就结束了,而汇编上也能看到:
所以说通过以上三种方式对synchronized关键字应该有比较深刻的印象了,实际上在在底层是叫Monitor。