java线程基础巩固---数据同步引入并结合jconsole,jstack以及汇编指令认识synchronized关键字

对于多线程编程而言其实老生成谈的就是数据同步问题,接下来就会开始接触这块的东东,比较麻烦,但是也是非常重要,所以按部就班的一点点去专研它,下面开始。

数据同步引入:

这里用之前写过的银行叫号的功能做为数据同步知识的引入,具体可以查看: 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。

posted on 2017-12-14 22:35  cexo  阅读(346)  评论(0编辑  收藏  举报

导航