Java多執行緒——Thread類
Java多執行緒——Thread類
Java 中執行緒實現方式有兩種:
- 繼承Thread類,並重寫run方法
- 實現Runnable介面的run方法
Thread類
使用方法:繼承Thread類,並重寫run方法
public class Demo { public static class MyThread extends Thread { public void run() { System.out.println("my thread"); } } public static void main(String[] args) { Thread mythread = new MyThread(); // 呼叫start()方法後,該執行緒才算啟動 mythread.start(); } }
Runable 介面
使用方法:實現Runnable介面的run方法
public class Demo1 { public static class MyThread implements Runnable { @Override public void run() { System.out.println("MyThread"); } } public static void main(String[] args) { new Demo.MyThread().start(); // Java 8 函數語言程式設計,可以省略MyThread類 new Thread(() -> { System.out.println("Java 8 匿名內部類"); }).start(); } }
Java執行緒狀態
Thread 類裡有一個列舉型別 State,定義了執行緒的幾種狀態,分別有:
- NEW:表示當前執行緒尚未啟動,NEW狀態表示例項化一個執行緒之後,但沒有開始執行,即 Thread 例項還沒呼叫 start() 方法。
- RUNNABLE:表示當前執行緒正在執行中,RUNNABLE 狀態包括了作業系統執行緒狀態中的 Running 和 Ready,處於此狀態的執行緒可能正在執行,也可能正在等待系統資源。
- BLOCKED:表示當前執行緒處於阻塞狀態,處於 BLOCKED 狀態的執行緒正等待鎖的釋放以進入同步區。
- WAITING:表示當前執行緒處於無限期等待狀態,處於這種狀態的執行緒不會被分配CPU執行時間,它們要等待顯示的被其它執行緒喚醒。
- TIMED_WAITING:表示當前執行緒處於超時等待狀態,處於這種狀態的執行緒也不會被分配 CPU 執行時間,不過無需等待被其它執行緒顯示的喚醒,在一定時間之後它們會由系統自動的喚醒。
- TERMINATED:表示當前執行緒處於終止狀態,已終止執行緒的執行緒狀態,執行緒已經結束執行,即 run() 方法執行完成。
Java8 中 state 列舉類程式碼:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
檢視執行緒當前狀態可以呼叫 getState() 方法,並進入:
/**
* Returns the state of this thread.
* This method is designed for use in monitoring of the system state,
* not for synchronization control.
*
* @return this thread's state.
* @since 1.5
*/
public State getState() {
// get current thread state
return sun.misc.VM.toThreadState(threadStatus);
}
// sun.misc.VM 原始碼:
public static State toThreadState(int var0) {
if ((var0 & 4) != 0) {
return State.RUNNABLE;
} else if ((var0 & 1024) != 0) {
return State.BLOCKED;
} else if ((var0 & 16) != 0) {
return State.WAITING;
} else if ((var0 & 32) != 0) {
return State.TIMED_WAITING;
} else if ((var0 & 2) != 0) {
return State.TERMINATED;
} else {
return (var0 & 1) == 0 ? State.NEW : State.RUNNABLE;
}
}
NEW:
例項化一個執行緒之後,並且這個執行緒沒有開始執行,這個時候的狀態就是 NEW,尚未啟動指的是還沒呼叫 Thread 例項的 start() 方法。
關於start() 的兩個引申問題:
- 反覆呼叫同一個執行緒的start()方法是否可行?
- 假如一個執行緒執行完畢(此時處於TERMINATED狀態),再次呼叫這個執行緒的start()方法是否可行?
檢視 start() 方法程式碼:
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* <code>start</code> method) and the other thread (which executes its
* <code>run</code> method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
在 start() 內部,這裡有一個 threadStatus 的變數。如果它不等於0,呼叫 start() 是會直接丟擲異常的。在呼叫一次start()之後,threadStatus 的值會改變(threadStatus !=0),此時再次呼叫 start() 方法會丟擲 IllegalThreadStateException 異常。
因此,兩個問題的答案都是不可行。
RUNNABLE:
表示當前執行緒正在執行中。處於RUNNABLE狀態的執行緒在Java虛擬機器中執行,也有可能在等待其他系統資源(比如I/O)。
RUNNABLE 狀態也可以理解為存活著正在嘗試徵用 CPU 的執行緒(有可能這個瞬間並沒有佔用CPU,但是它可能正在傳送指令等待系統排程)。由於在真正的系統中,並不是開啟一個執行緒後,CPU就只為這一個執行緒服務,它必須使用許多排程演算法來達到某種平衡,不過這個時候執行緒依然處於RUNNABLE狀態。
BLOCKED:
BLOCKED 稱為阻塞狀態,原因通常是它在等待一個“鎖”,當嘗試進入一個 synchronized 語句塊/方法時,鎖已經被其它執行緒佔有,就會被阻塞,直到另一個執行緒走完臨界區或發生了相應鎖物件的 wait() 操作後,它才有機會去爭奪進入臨界區的權利。
處於 BLOCKED 狀態的執行緒,即使對其呼叫 thread.interrupt() 也無法改變其阻塞狀態,因為 interrupt() 方法只是設定執行緒的中斷狀態,即做一個標記,不能喚醒處於阻塞狀態的執行緒。
注意:
ReentrantLock.lock() 操作後進入的是 WAITING 狀態,其內部呼叫的是 LockSupport.park() 方法。
WAITING:
處於這種狀態的執行緒不會被分配CPU執行時間,它們要等待顯示的被其它執行緒喚醒。WAITING 狀態通常是指一個執行緒擁有物件鎖後進入到相應的程式碼區域後,呼叫相應的“鎖物件”的 wait() 方法操作後產生的一種結果。變相的實現還有 LockSupport.park()、Thread.join()等,它們也是在等待另一個事件的發生,也就是描述了等待的意思。
以下方法會讓執行緒陷入無限期等待狀態:
-
沒有設定timeout引數的Object.wait()
-
沒有設定timeout引數的Thread.join()
-
LockSupport.park()
注意:
LockSupport.park(Object blocker) 會掛起當前執行緒,引數blocker是用於設定當前執行緒的“volatile Object parkBlocker 成員變數”
parkBlocker 是用於記錄執行緒是被誰阻塞的,可以通過LockSupport.getBlocker()獲取到阻塞的物件,用於監控和分析執行緒用的。
“阻塞”與“等待”的區別:
-
“阻塞”狀態是等待著獲取到一個排他鎖,進入“阻塞”狀態都是被動的,離開“阻塞”狀態是因為其它執行緒釋放了鎖,不阻塞了;
-
“等待”狀態是在等待一段時間 或者 喚醒動作的發生,進入“等待”狀態是主動的
如主動呼叫 Object.wait(),如無法獲取到 ReentraantLock,主動呼叫 LockSupport.park(),如主執行緒主動呼叫 subThread.join(),讓主執行緒等待子執行緒執行完畢再執行。離開“等待”狀態是因為其它執行緒發生了喚醒動作或者到達了等待時間。
TIMED_WAITING:
處於這種狀態的執行緒也不會被分配CPU執行時間,不過無需等待被其它執行緒顯示的喚醒,在一定時間之後它們會由系統自動的喚醒。
以下方法會讓執行緒進入TIMED_WAITING限期等待狀態:
-
Thread.sleep()方法
-
設定了timeout引數的Object.wait()方法
-
設定了timeout引數的Thread.join()方法
-
LockSupport.parkNanos()方法
-
LockSupport.parkUntil()方法
TERMINATED:
已終止執行緒的執行緒狀態,執行緒已經結束執行。這個狀態僅僅是 Java 語言提供的一個狀態,在作業系統內部可能已經登出了相應的執行緒,或者將它複用給其他需要使用執行緒的請求,而在Java語言級別只是通過Java程式碼看到的執行緒狀態而已。
執行緒狀態的轉換
執行緒狀態轉換圖:
BLOCKED狀態與RUNNABLE狀態的轉換
處於BLOCKED狀態的執行緒是因為在等待鎖的釋放。假如這裡有兩個執行緒a和b,a執行緒提前獲得了鎖並且暫未釋放鎖,此時b就處於BLOCKED狀態。
同時,run() 方法的執行是需要時間的,並不是啟動 start() 方法後就會立即執行,檢視以下例子:
public class Demo2 {
public void blockedTest() {
Thread a = new Thread(new Runnable() {
@Override
public void run() {
testMethod();
}
}, "a");
Thread b = new Thread(new Runnable() {
@Override
public void run() {
testMethod();
}
}, "b");
a.start();
b.start();
System.out.println(a.getName() + ":" + a.getState()); // 輸出?
System.out.println(b.getName() + ":" + b.getState()); // 輸出?
}
// 同步方法爭奪鎖
private synchronized void testMethod() {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
demo2.blockedTest();
}
}
輸出結果:此時 a 執行緒已得到鎖,進入 run() 方法,但還沒有執行 Thread.sleep(),由於 a 執行緒已得到鎖,b 執行緒等待鎖的釋放,進入 BLOCKED 狀態。
a:RUNNABLE
b:BLOCKED
Process finished with exit code 0
調換語句順序,發現結果發生變化:
public void blockedTest() throws InterruptedException {
... ...
a.start();
System.out.println(a.getName() + ":" + a.getState()); // 輸出?
b.start();
System.out.println(b.getName() + ":" + b.getState()); // 輸出?
}
輸出結果:原因是測試方法的main執行緒只保證了a,b兩個執行緒呼叫start()方法(轉化為RUNNABLE狀態),還沒等兩個執行緒真正開始爭奪鎖,就已經列印此時兩個執行緒的狀態(RUNNABLE)了。
a:RUNNABLE
b:RUNNABLE
Process finished with exit code 0
讓 main 執行緒停一段時間,就可以得到 BLOCKED 狀態的結果:
public void blockedTest() throws InterruptedException {
... ...
a.start();
Thread.sleep(200L);
System.out.println(a.getName() + ":" + a.getState()); // 輸出?
b.start();
Thread.sleep(200L);
System.out.println(b.getName() + ":" + b.getState()); // 輸出?
}
輸出結果:此時 a 執行緒已得到鎖,正在執行 run() 方法,進入 TIMED_WAITING 狀態,b 執行緒由於得不到鎖,進入 BLOCKED 狀態
a:TIMED_WAITING
b:BLOCKED
Process finished with exit code 0
WAITING狀態與RUNNABLE狀態的轉換
有3個方法可以使執行緒從RUNNABLE狀態轉為WAITING狀態。
Thread.join():
public void blockedTest() {
······
a.start();
a.join();
b.start();
System.out.println(a.getName() + ":" + a.getState()); // 輸出 TERMINATED
System.out.println(b.getName() + ":" + b.getState());
}
輸出結果:呼叫join()方法不會釋放鎖,會一直等待當前執行緒執行完畢(轉換為TERMINATED狀態)。b執行緒的狀態,有可能列印RUNNABLE(尚未進入同步方法),也有可能列印TIMED_WAITING(進入了同步方法)。
a:TERMINATED
b:TIMED_WAITING
Process finished with exit code 0
需要注意的是,其他執行緒呼叫notify()方法只會喚醒單個等待鎖的執行緒,如有有多個執行緒都在等待這個鎖的話不一定會喚醒到之前呼叫wait()方法的執行緒。
Object.wait():導致執行緒進入等待狀態,直到它被其他執行緒通過notify()或者notifyAll喚醒。該方法只能在同步方法中呼叫。如果當前執行緒不是鎖的持有者,該方法丟擲一個IllegalMonitorStateException異常。
使用示例:
public class WaitNotifyTest {
public static void main(String[] args) {
Object lock = new Object();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("執行緒A等待獲取lock鎖");
synchronized (lock) {
try {
System.out.println("執行緒A獲取了lock鎖");
Thread.sleep(1000);
System.out.println("執行緒A將要執行lock.wait()方法進行等待");
lock.wait();
System.out.println("執行緒A等待結束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("執行緒B等待獲取lock鎖");
synchronized (lock) {
System.out.println("執行緒B獲取了lock鎖");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("執行緒B將要執行lock.notify()方法進行通知");
lock.notify();
}
}
}).start();
}
}
輸出結果:
執行緒A等待獲取lock鎖
執行緒A獲取了lock鎖
執行緒B等待獲取lock鎖
執行緒A將要執行lock.wait()方法進行等待
執行緒B獲取了lock鎖
執行緒B將要執行lock.notify()方法進行通知
執行緒A等待結束
Process finished with exit code 0
LockSupport.park():LockSupport.park() 呼叫的是 Unsafe 中的 native 程式碼,與Object類的wait/notify機制相比,park 以 thread 為操作物件更符合阻塞執行緒的直觀定義,操作更精準,可以準確地喚醒某一個執行緒增加了靈活性。
//LockSupport中
public static void park() {
UNSAFE.park(false, 0L);
}
TIMED_WAITING狀態與RUNNABLE狀態轉換
TIMED_WAITING 與 WAITING 狀態類似,只是 TIMED_WAITING 狀態等待的時間是指定的。
Thread.sleep(long):
使當前執行緒睡眠指定時間。需要注意這裡的“睡眠”只是暫時使執行緒停止執行,並不會釋放鎖。時間到後,執行緒會重新進入RUNNABLE狀態。
Sleep 方法程式碼:
public static native void sleep(long millis) throws InterruptedException;
// 納秒級別控制
public static void sleep(long millis, int nanos) throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
Object.wait(long):
wait(long) 方法使執行緒進入 TIMED_WAITING 狀態。這裡的wait(long)方法與無參方法 wait() 相同的地方是,都可以通過其他執行緒呼叫 notify() 或 notifyAll() 方法來喚醒。
不同的地方是,有參方法 wait(long) 就算其他執行緒不來喚醒它,經過指定時間 long 之後它會自動喚醒,擁有去爭奪鎖的資格。
Thread.join(long):
join(long) 使當前執行緒執行指定時間,並且使執行緒進入 TIMED_WAITING 狀態。
join(long) 的使用:呼叫 a.join(1000L),因為是指定了具體 a 執行緒執行的時間的,並且執行時間是小於 a 執行緒 sleep 的時間,所以 a 執行緒狀態輸出 TIMED_WAITING,b 執行緒狀態仍然不固定(RUNNABLE 或 BLOCKED)。
public void blockedTest() {
······
a.start();
a.join(1000L);
b.start();
System.out.println(a.getName() + ":" + a.getState()); // 輸出 TIEMD_WAITING
System.out.println(b.getName() + ":" + b.getState());
}
Java執行緒中斷
Thread類裡提供的關於執行緒中斷的幾個方法:
- Thread.interrupt():中斷執行緒。這裡的中斷執行緒並不會立即停止執行緒,而是設定執行緒的中斷狀態為true(預設是flase);
- Thread.interrupted():測試當前執行緒是否被中斷。執行緒的中斷狀態受這個方法的影響,意思是呼叫一次使執行緒中斷狀態設定為true,連續呼叫兩次會使得這個執行緒的中斷狀態重新轉為false;
- Thread.isInterrupted():測試當前執行緒是否被中斷。與上面方法不同的是呼叫這個方法並不會影響執行緒的中斷狀態。
“VisualVM執行緒監控執行緒狀態”與“Java執行緒狀態”對應關係總結:
通過 dump thread stack,並與 VisualVM 監控資訊中的執行緒名稱對應,找到的 VisualVM 每種執行緒狀態的執行緒堆疊如下:
1、執行:RUNNABLE
"http-bio-8080-Acceptor-0" daemon prio=6 tid=0x000000000d7b4800 nid=0xa264 runnable [0x000000001197e000]
java.lang.Thread.State: RUNNABLE
at java.net.DualStackPlainSocketImpl.accept0(Native Method)
at java.net.DualStackPlainSocketImpl.socketAccept(DualStackPlainSocketImpl.java:131)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:398)
at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:199)
- locked <0x00000000c2303850> (a java.net.SocksSocketImpl)
at java.net.ServerSocket.implAccept(ServerSocket.java:530)
at java.net.ServerSocket.accept(ServerSocket.java:498)
at org.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)
at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:220)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
2、休眠:sleeping
"Druid-ConnectionPool-Destory-293325558" daemon prio=6 tid=0x000000000d7ad000 nid=0x9c94 waiting on condition [0x000000000bf0f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at com.alibaba.druid.pool.DruidDataSource$DestroyConnectionThread.run(DruidDataSource.java:1685)
Locked ownable synchronizers:
- None
3、等待:wait
"Finalizer" daemon prio=8 tid=0x0000000009349000 nid=0xa470 in Object.wait() [0x000000000a82f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000c22a0108> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
- locked <0x00000000c22a0108> (a java.lang.ref.ReferenceQueue.Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
Locked ownable synchronizers:
- None
"JMX server connection timeout 45" daemon prio=6 tid=0x000000000e846000 nid=0xab10 in Object.wait() [0x00000000137df000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000c55da3f0> (a [I)
at com.sun.jmx.remote.internal.ServerCommunicatorAdmin$Timeout.run(ServerCommunicatorAdmin.java:168)
- locked <0x00000000c55da3f0> (a [I)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
4、駐留:park
"http-bio-8080-exec-2" daemon prio=6 tid=0x000000000d7b8000 nid=0x9264 waiting on condition [0x000000000ee4e000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000c5629bc8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.util.concurrent.ThreadPoolExecutor.Worker.run(ThreadPoolExecutor.java:615)
at org.apache.tomcat.util.threads.TaskThread.WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
"pool-9-thread-1" prio=6 tid=0x000000000d7b2000 nid=0xd5fc waiting on condition [0x000000001187e000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000c563b9e0> (a java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:226)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2082)
at java.util.concurrent.ScheduledThreadPoolExecutor.DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1090)
at java.util.concurrent.ScheduledThreadPoolExecutor.DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:807)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.util.concurrent.ThreadPoolExecutor.Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
5、監視:monitor
"Thread-1" prio=6 tid=0x000000000a8a1800 nid=0xfdb4 waiting for monitor entry [0x000000000b4de000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.Test2$T.run(Test2.java:58)
- waiting to lock <0x00000000eab757e0> (a java.lang.Object)
Locked ownable synchronizers:
- None
彙總如下圖所示: