• 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。

  • 相关阅读:
    一百多套开发视频教程的下载地址
    魅族MX3问题集锦
    Entity Framework 5问题集锦
    【PHP Manager for IIS】让IIS支持PHP
    MySQL安装
    phpMyAdmin安装
    犯了一个愚蠢的序列化错误
    最佳策略
    .net非托管资源
    .net内存何时回收?
  • 原文地址:https://www.cnblogs.com/webor2006/p/8040369.html
Copyright © 2020-2023  润新知