1. 程式人生 > >JVM除錯常用命令——jstack命令與執行緒狀態(3)

JVM除錯常用命令——jstack命令與執行緒狀態(3)

(接上文《JVM除錯常用命令——jstack命令與Java執行緒棧(2)》)

2.1.3.2、當前執行緒呼叫目前執行緒的join方法,等待後者執行完成

join方法可以讓一個執行緒持續等待到另一個執行緒完成執行後,再繼續進行執行。下面我們就來看一下使用join方法讓一個執行緒進入WATING狀態時,在jstack命令結果中的列印效果。

// ......之前的程式碼片段省略
public static void main(String[] args) {
  Thread thread1 = new Thread(new MyThread1(), "thread1");
  Thread thread2 =
new Thread(new MyThread2(thread1), "thread2"); thread1.start(); thread2.start(); } private static class MyThread2 implements Runnable { private Thread joinThread; public MyThread2(Thread joinThread) { this.joinThread = joinThread; } @Override public void run() { Thread currentThread =
Thread.currentThread(); System.out.println("當前執行緒[" + currentThread.getName() + "]使用join進入等待"); try { this.joinThread.join(); } catch (InterruptedException e) { e.printStackTrace(System.out); } System.out.println("當前執行緒[" + currentThread.getName() + "]已經完成等待"); } }
private static class MyThread1 implements Runnable { @Override public void run() { Thread currentThread = Thread.currentThread(); System.out.println("當前執行緒[" + currentThread.getName() + "]正在執行"); } } // ......之後的程式碼片段省略

以上程式碼,我們建立了兩個執行緒Thread1和Thread2,其中Thread2會使用join方法,等待Thread1執行完成後在執行。我們使用debug方式,將以上程式碼的執行效果停滯於如下圖所示的狀態:

在這裡插入圖片描述

如上圖所示的效果,我們將MyThread2類的例項執行到第21行,也就是呼叫join方法的位置,這時MyThread2的執行緒就會進入WATING狀態;而MyThread1類的例項執行到第33行,也就是剛完成System.out方法,但是整個方法還沒有執行結束的位置,這時MyThread1類的例項處於RUNNABLE狀態。接著我們執行jstack命令驗證執行緒狀態,如下:

>jstack 289476
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode):
.........
"thread2" #15 prio=5 os_prio=0 tid=0x000000001ec53000 nid=0x46064 in Object.wait() [0x000000001fc5f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076c5c4910> (a java.lang.Thread)
        at java.lang.Thread.join(Thread.java:1252)
        - locked <0x000000076c5c4910> (a java.lang.Thread)
        at java.lang.Thread.join(Thread.java:1326)
        at testThread.TestJoin$MyThread2.run(TestJoin.java:25)
        at java.lang.Thread.run(Thread.java:748)
"thread1" #14 prio=5 os_prio=0 tid=0x000000001ec52000 nid=0x46054 at breakpoint[0x000000001fb5f000]
   java.lang.Thread.State: RUNNABLE
        at testThread.TestJoin$MyThread1.run(TestJoin.java:36)
        at java.lang.Thread.run(Thread.java:748)
.........

從命令結果可以看到,名為thread2的執行緒在“TestJoin$MyThread2”類定義的25行呼叫了join方法,並在join方法中獲得了物件(0x000000076c5c4910)的操作權,繼續執行join方法中的內容,到達了第1252行。在這一行呼叫了wait方法釋放掉物件(0x000000076c5c4910)的操作權,並進入WATING狀態。所以我們可以看當名為thread2的執行緒進入WAITING狀態後,其狀態說明資訊中被標註為“(on object monitor)”,這是因為這種WAITING狀態的本質和2.1.3.1中介紹的呼叫wait方法進入WAITING狀態的本質是一樣的。另外,從join方法的主要程式碼塊中,我們也可以讀到相佐證的原始碼:

在這裡插入圖片描述

2.1.3.3、可重入鎖(包括讀寫分離鎖)呼叫lock方法,並觸發阻塞效果

ReentrantLock和ReentrantReadWriteLock都是AQS同步框架的一個具體實現(關於AQS框架,我們將會在本專題後續的文章中進行詳細講解),那麼基於ReentrantLock或者ReentrantReadWriteLock產生的“阻塞”效果又是怎樣呢?首先上程式碼:

// ......之前的程式碼片段省略
public static void main(String[] args) {
  ReentrantLock myLock = new ReentrantLock();
  // 產生兩個執行緒,使用debug方式,觀察特定效果
  Thread thread1 = new Thread(new MyThread(myLock), "thread1");
  Thread thread2 = new Thread(new MyThread(myLock), "thread2");
  thread1.start();
  thread2.start();
}
private static class MyThread implements Runnable {
  private ReentrantLock myLock;
  MyThread(ReentrantLock myLock) {
    this.myLock = myLock;
  }
  @Override
  public void run() {
    String threadName = Thread.currentThread().getName();
    try {
      myLock.lock();
      System.out.println("thread[" + threadName + "]正在工作......");
    } finally {
      myLock.unlock();
    }
  }
}
// ......之後的程式碼片段省略

之上的程式碼片段很簡單了,不需要再進行過多的解釋,如果讀者對ReentrantLock和ReentrantReadWriteLock不清楚,那麼建議先閱讀網路資料。通過debug模式,我們將以上程式碼停止在以下狀態:

執行緒名為thread1的執行緒呼叫lock方法後,執行到“System.out.println”這句程式碼;由於這時thread1還沒有呼叫unlock方法,所以當執行緒thread2呼叫lock方法時,將會進入阻塞狀態。如下圖所示:

在這裡插入圖片描述

細心的讀者可以發現,在以上使用ReentrantLock或者ReentrantReadWriteLock的除錯資訊中,除錯視窗示意的兩個執行緒好像並沒有提示開發人員“某個執行緒持有了某個物件的鎖”,既是沒有那把“黃色的鎖”,通過jstack命令我們也可以觀察到這一現象:

# jstack 336144

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode):
"thread2" #15 prio=5 os_prio=0 tid=0x000000001ea40000 nid=0x462b4 waiting on condition [0x000000001fa4e000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076c5c60b8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
        at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
        at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
        at testThread.TestReentrantLock$MyThread.run(TestReentrantLock.java:27)
        at java.lang.Thread.run(Thread.java:748)
		
"thread1" #14 prio=5 os_prio=0 tid=0x000000001ea3f000 nid=0x47cf0 runnable [0x000000001f94e000]
   java.lang.Thread.State: RUNNABLE
        at testThread.TestReentrantLock$MyThread.run(TestReentrantLock.java:28)
        at java.lang.Thread.run(Thread.java:748)

如以上命令結果所示,名叫thread1和thread2的執行緒確實沒有被提示“locked”住了某個物件,只有進入“阻塞”狀態的thread2有一個parking標識——但是thread2確實進入了阻塞狀態,並在在thread呼叫了unlock方法後確實又能夠恢復到RUNNABLE狀態。這實際上就是AQS同步框架的工作效果(後文詳細講解)——這時讀者可以觀察到在WAITING狀態後的標識使用的是“(parking)”而不是“object monitor”。

2.1.3.4、使用LockSupport,並觸發阻塞效果

LockSupport屬於AQS框架的底層支援部分,LockSupport提供了執行緒元語級別的執行/阻塞控制,能夠幫助開發者完成類似ReentrantLock、Semaphore等這樣基於AQS框架的高級別同步工具。有了2.1.3.3中對ReentrantLock阻塞效果的分析後,這裡我們大致檢驗一下直接使用LockSupport讓指定執行緒進入阻塞模式後的效果。同樣,先上程式碼片段(在這裡,讀者如果不清楚LockSupport中主要方法的功能也無所謂,後文將進行詳細介紹):

// ......之前的程式碼片段省略
private static Object lockObject = new Object();
// 相信不用寫太多註釋了吧。。。。
public static void main(String[] args) {
  Thread thread2 = new Thread(new MyThread2(), "thread2");
  Thread thread1 = new Thread(new MyThread1(thread2), "thread1");
  thread1.start();
  thread2.start();
}
private static class MyThread1 implements Runnable {
  private Thread targetThread;
  public MyThread1(Thread targetThread) {
    this.targetThread = targetThread;
  }
  @Override
  public void run() {
    LockSupport.unpark(targetThread);
    System.out.println("// do somethings");
  }
}
private static class MyThread2 implements Runnable {
  @Override
  public void run() {
    LockSupport.park(lockObject);
    System.out.println("// do somethings");
  }
}
// ......之後的程式碼片段省略

通過debug模式的支援,我們先讓MyThread2呼叫LockSupport.park方法進入阻塞狀態,MyThread1中的LockSupport.unpark方法可以讓前者解除阻塞狀態,但是我們還不忙執行。這時通過jstack命令觀察程序中主要的執行緒狀態如下:

jstack 341584
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode):
"thread2" #14 prio=5 os_prio=0 tid=0x000000001ea99000 nid=0x53624 waiting on condition [0x000000001faae000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x000000076c5c4f60> (a java.lang.Object)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at testThread.TestLockSupport$MyThread2.run(TestLockSupport.java:34)
        at java.lang.Thread.run(Thread.java:748)
"thread1" #15 prio=5 os_prio=0 tid=0x000000001ea98800 nid=0x532d0 at breakpoint[0x000000001f9af000]
   java.lang.Thread.State: RUNNABLE
        at testThread.TestLockSupport$MyThread1.run(TestLockSupport.java:26)
        at java.lang.Thread.run(Thread.java:748)

這時我們看到的效果和前文中使用ReentrantLock使執行緒進入“阻塞”狀態的效果是一致的。

====================================
(接下文)