個人博客請訪問 http://www.x0100.top
1. 創建啓動線程
兩種方法
創建和啓動線程兩種方法:繼承 Thread 類、實現 Runable 接口。
方法一:繼承 Thread
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("創建線程");
}
}
方法二:實現 Runable
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("創建線程");
}
}
創建線程實現
通過查看 Thread 類代碼,不管是繼承 Thread 類創建線程還是實現 Runable 創建線程,都是調用 Thread 類的 init 方法,來看下 init 方法:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 當前線程就是此新線程的父線程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
// 新線程繼承了parent線程的group、是否爲Daemon、優先級priority、加載資源的contextClassLoader、可繼承的ThreadLocal。
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext = acc != null ? acc
: AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal
.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
// parent線程爲新線程分配一個唯一的ID
tid = nextThreadID();
}
通過 init 方法可以看出:
-
新構造的線程對象是由其 parent 線程來進行空間分配的。
-
新線程繼承了 parent 線程的 group、是否爲 Daemon、優先級 priority、加載資源的 contextClassLoader、可繼承的 ThreadLocal。
-
parent 線程會分配一個唯一的 ID 來標識這個 child 新線程。
啓動線程
start()方法啓動線程。
線程 start()方法的含義是:當前線程(即 parent 線程)同步告知 Java 虛擬機,只要線程規劃器空閒,應立即啓動調用 start()方法的線程。
注意:
-
啓動線程調用 start()方法,而不是 run()。
-
啓動一個線程前,最好爲這個線程設置線程名稱,因爲這樣在使用 jstack 分析程序或者進行問題排查時,就會給開發人員提供一些提示,自定義的線程最好能夠起個名字。
2. 線程狀態
狀態
1. 新建狀態(NEW)
當程序使用 new 關鍵字創建了一個線程之後,線程就處於新建狀態,此時的線程情況如下:
-
此時 JVM 爲其分配內存,並初始化其成員變量的值;
-
此時線程對象沒有表現出任何線程的動態特徵,程序也不會執行線程的線程執行體;
2. 就緒狀態(RUNNABLE)
當線程對象調用了 start()方法之後,線程處於就緒狀態。此時的線程情況如下:
-
此時 JVM 會爲其創建方法調用棧和程序計數器;
-
線程並沒有開始運行,而是等待系統爲其分配 CPU 時間片;
3. 運行狀態(RUNNING)
當線程獲得了 CPU 時間片,CPU 調度處於就緒狀態的線程並執行 run()方法的線程執行體,則該線程處於運行狀態。
如果計算機只有一個CPU,那麼在任何時刻只有一個線程處於運行狀態;
如果在一個多處理器的機器上,將會有多個線程並行執行,處於運行狀態;
當線程數大於處理器數時,依然會存在多個線程在同一個CPU上輪換的現象;
對於採用搶佔式策略的系統而言,系統會給每個可執行的線程分配一個時間片來處理任務;當該時間片用完後,系統就會剝奪該線程所佔用的資源,讓其他線程獲得執行的機會。此時線程就會又從運行狀態變爲就緒狀態,重新等待系統分配資源。
4. 阻塞狀態(BLOCKED)
處於運行狀態的線程在某些情況下,讓出 CPU 並暫時停止自己的運行,進入阻塞狀態。如:線程阻塞於 synchronized 鎖。
5. 等待狀態(WAITING)
線程處於無限制等待狀態,等待一個特殊的事件來重新喚醒,喚醒線程之後進入就緒狀態,如:
通過wait()方法進行等待的線程等待一個notify()或者notifyAll()方法;
通過join()方法進行等待的線程等待目標線程運行結束而喚醒;
阻塞在 java.concurrent 包中 Lock 接口的線程狀態不是 BLOCK 狀態,而是 WAITING 等待狀態,因爲 java.concurrent 包中 Lock 接口對於阻塞的實現均使用了 LockSupport 類中的相關方法。
6. 超時等待狀態(TIMED_WAITING)
線程進入了一個時限等待狀態,如:sleep(3000),等待 3 秒後線程重新進入就緒狀態。
7. 死亡狀態(DEAD)
線程會以如下 3 種方式結束,結束後就處於死亡狀態:
① run()或 call()方法執行完成,線程正常結束;
② 線程拋出一個未捕獲的 Exception 或 Error;
③ 直接調用該線程 stop()方法來結束該線程—該方法容易導致死鎖,通常不推薦使用;
狀態轉換
線程狀態轉換——《Java併發編程藝術》
查看線程狀態
Java 服務器問題排查中,經常需要查看線程狀態來定位問題。
查看如下示例代碼的線程運行狀態:
public class ThreadState {
public static void main(String[] args) {
new Thread(new RunThread(), "RunThread").start();// 一直處於RUNNABLE線程
new Thread(new TimeWaiting(), "TimeWaitingThread").start();// TIMED_WAITING線程
new Thread(new Waiting(), "WaitingThread").start();// WAITING線程
new Thread(new Blocked(), "BlockedThread-1").start();// 獲取鎖之後TIMED_WAITING
new Thread(new Blocked(), "BlockedThread-2").start();// 獲取不到鎖,被阻塞BLOCKED
}
// 該線程一直運行
static class RunThread implements Runnable {
@Override
public void run() {
while (true) {
System.out.println(1);
}
}
}
// 該線程不斷地進行睡眠 TIMED_WAITING
static class TimeWaiting implements Runnable {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 該線程在Waiting.class實例上等待 WAITING
static class Waiting implements Runnable {
@Override
public void run() {
while (true) {
synchronized (Waiting.class) {
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 該線程在Blocked.class實例上加鎖後,不會釋放該鎖
static class Blocked implements Runnable {
public void run() {
synchronized (Blocked.class) {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
-
Export 導出 jar 包 ThreadState.jar
-
Linux 環境或者 windows 的命令界面,運行 ThreadState.jar。
-
執行命令
java -cp ThreadState.jar test.ThreadState
-
到 java 安裝目錄(我的目錄是:/usr/java/jdk1.8.0_161/bin)下,執行 ./jps ,顯示如下:
5435 ThreadState
5435 就是 ThreadState.jar 程序的進程 id
-
執行命令 ./jstack 5435 ,查看線程狀態
// 名爲"BlockedThread-2" 的線程狀態:TIMED_WAITING
"BlockedThread-2" #13 prio=5 os_prio=0 tid=0x00007fa8f0157800 nid=0x154f waiting on condition [0x00007fa8c962e000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at test.ThreadState$Blocked.run(ThreadState.java:61)
- locked <0x00000000d9408370> (a java.lang.Class for test.ThreadState$Blocked)
at java.lang.Thread.run(Thread.java:748)
// 名爲"BlockedThread-1" 的線程狀態:BLOCKED
"BlockedThread-1" #12 prio=5 os_prio=0 tid=0x00007fa8f0156000 nid=0x154e waiting for monitor entry [0x00007fa8c972f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at test.ThreadState$Blocked.run(ThreadState.java:61)
- waiting to lock <0x00000000d9408370> (a java.lang.Class for test.ThreadState$Blocked)
at java.lang.Thread.run(Thread.java:748)
// 名爲"WaitingThread" 的線程狀態:WAITING
"WaitingThread" #11 prio=5 os_prio=0 tid=0x00007fa8f0154000 nid=0x154d in Object.wait() [0x00007fa8c9830000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000d9408560> (a java.lang.Class for test.ThreadState$Waiting)
at java.lang.Object.wait(Object.java:502)
at test.ThreadState$Waiting.run(ThreadState.java:46)
- locked <0x00000000d9408560> (a java.lang.Class for test.ThreadState$Waiting)
at java.lang.Thread.run(Thread.java:748)
// 名爲"TimeWaitingThread" 的線程狀態:TIMED_WAITING
"TimeWaitingThread" #10 prio=5 os_prio=0 tid=0x00007fa8f0152800 nid=0x154c waiting on condition [0x00007fa8c9931000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at test.ThreadState$TimeWaiting.run(ThreadState.java:31)
at java.lang.Thread.run(Thread.java:748)
// 名爲"RunThread" 的線程狀態:RUNNABLE
"RunThread" #9 prio=5 os_prio=0 tid=0x00007fa8f0150800 nid=0x154b runnable [0x00007fa8c9a32000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
- locked <0x00000000d942e020> (a java.io.BufferedOutputStream)
at java.io.PrintStream.write(PrintStream.java:482)
- locked <0x00000000d9404b30> (a java.io.PrintStream)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
- locked <0x00000000d9404ae8> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.write(PrintStream.java:527)
- eliminated <0x00000000d9404b30> (a java.io.PrintStream)
at java.io.PrintStream.print(PrintStream.java:597)
at java.io.PrintStream.println(PrintStream.java:736)
- locked <0x00000000d9404b30> (a java.io.PrintStream)
at test.ThreadState$RunThread.run(ThreadState.java:20)
at java.lang.Thread.run(Thread.java:748)
總結
Java 中創建線程兩種方法:繼承 Thread 和實現 Runable。線程的啓動調用 start 方法,線程執行 run 方法。
新構造的線程對象是由其 parent 線程來進行空間分配的和線程 id 的,新線程繼承 parent 線程的 group、是否爲 Daemon、優先級 priority 等屬性。
線程的狀態:NEW、RUNNABLE、RUNNING、BLOCKED、WAITING、TIMED_WAITING、DEAD。
Java 服務器問題排查過程中,常用的 jstack 命令查看線程狀態。