对于多线程编程而言其实老生成谈的就是数据同步问题,接下来就会开始接触这块的东东,比较麻烦,但是也是非常重要,所以按部就班的一点点去专研它,下面开始。
数据同步引入:
这里用之前写过的银行叫号的功能做为数据同步知识的引入,具体可以查看: 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。