建立和運作線程
- 方法一:直接使用Thread
// 建立線程對象
Thread t = new Thread("name") {
public void run() {
// 要執行的任務
}
};
// 啟動線程
t.start();
// Java 8 以後可以使用lambda精簡代碼
Thread t = new Thread(()->{
// 要執行的代碼
});
- 方法二:使用Runnable配合Thread
把【線程】與【任務】(要執行的代碼)分開,Thread代表線程,Runnable可運作的任務(線程要執行的代碼)
Runnable runnable = new Runnable() {
public void run() {
// 要執行的任務
}
};
// 建立線程對象 參數1:任務對象 參數2:線程名字
Thread t = new Thread(runnable, "name");
// 啟動線程
t.start();
- 小結
- 方法一把線程和任務合并在了一起,方法二是把線程和任務分開了
- 用Runnable更容易與線程池等進階API配合
- 用Runnable讓任務類脫離了Thread繼承體系,更靈活
- 方法三:FutureTask配合Thread
// 建立任務對象
FutureTask<Integer> task3 = new FutureTask<>(()->{
log.debug("hello");
return 100;
});
// 參數1 是任務對象; 參數2 是線程名字,推薦
new Thread(task3, "t3").start();
// 主線程阻塞,同步等待 task 執行完畢的結果
Integer result = task3.get();
log.debug("結果是:{}", result);
原理之線程運作
棧與棧幀
JVM中由堆、棧、方法區所組成,其中棧記憶體給線程用,每個線程啟動後,虛拟機就會為其配置設定一塊棧記憶體。
- 每個棧由多個棧幀(Frame)組成,對應着每次方法調用時所占用的記憶體
- 每個線程隻能有一個活動棧幀,對應着目前正在執行的那個方法
線程上下文切換
因為以下一些原因導緻CPU不再執行目前的線程,轉而執行另一個線程的代碼:
- 被動
- 線程的CPU時間片用完
- 垃圾回收
- 有更高優先級的線程需要運作
- 主動
- 線程自己調用了sleep、yield、wait、join、park、synchronized、lock等方法
當Context Switch發生時,需要由作業系統儲存目前線程的狀态,并恢複另一個線程的狀态,Java中對應的概念就是程式計數器(Program Counter Register), 它的作用是記住下一條jvm指令的執行位址,是線程私有的
- 狀态包括程式計數器、虛拟機棧中每個棧幀的資訊,如局部變量、操作數棧、傳回位址等
- Context Switch頻繁發生會影響性能
常見方法
方法名 | static | 功能說明 | 注意 |
---|---|---|---|
start() | 啟動一個新線程,在新的線程運作 run 方法中的代碼 | start 方法隻是讓線程進入就緒,裡面代碼不一定立刻運作(CPU 的時間片還沒分給它)。每個線程對象的start方法隻能調用一次,如果調用了多次會出現IllegalThreadStateException | |
run() | 新線程啟動後會調用的方法 | 如果在構造 Thread 對象時傳遞了 Runnable 參數,則線程啟動後會調用 Runnable 中的 run 方法,否則預設不執行任何操作。但可以建立 Thread 的子類對象,來覆寫預設行為 | |
join() | 等待線程運作結束 | ||
join(long n ) | 等待線程運作結束,最多等待 n毫秒 | ||
getId() | 擷取線程長整型的 id | id唯一 | |
getName() | 擷取線程名 | ||
setName(String) | 修改線程名 | ||
getPriority() | 擷取線程優先級 | ||
setPriority(int) | 修改線程優先級 | java中規定線程優先級是1~10 的整數,較大的優先級能提高該線程被 CPU 排程的機率 | |
getState() | 擷取線程狀态 | Java 中線程狀态是用 6 個 enum 表示,分别為:NEW, RUNNABLE, BLOCKED, WAITING,TIMED_WAITING, TERMINATED | |
isInterrupted() | 判斷是否被打斷 | 不會清除打斷标記 | |
isAlive() | 線程是否存活(還沒有運作完畢) | ||
interrupt() | 打斷線程 | 如果被打斷線程正在 sleep,wait,join 會導緻被打斷的線程抛出 InterruptedException,并清除 打斷标記 ;如果打斷的正在運作的線程,則會設定 打斷标記 ;park 的線程被打斷,也會設定 打斷标記 | |
interrupted() | static | 判斷目前線程是否被打斷 | 會清除 打斷标記 |
currentThread() | static | 擷取目前正在執行的線程 | |
sleep(long n) | static | 讓目前執行的線程休眠n毫秒,休眠時讓出 cpu的時間片給其它線程 | |
yield() | static | 提示線程排程器讓出目前線程對CPU的使用 | 主要是為了測試和調試 |
sleep與yield
- sleep
- 調用sleep會讓目前線程從 Running 進入 Timed Waiting 狀态(阻塞)
- 其他線程可以使用 interrupt 方法打斷正在睡眠的線程,這時 sleep 方法會抛出 InterruptedException
- 睡眠結束後的線程未必會立刻得到執行
- 建議用TimeUnit的sleep代替Thread的sleep來獲得更好的可讀性
- yield
- 調用 yield 會讓目前線程從 Running 進入 Runnable 就緒狀态,然後排程執行其它線程
- 具體的實作依賴于作業系統的任務排程器
線程優先級
- 線程優先級會提示(hint)排程器優先排程該線程,但它僅僅是一個提示,排程器可以忽略它
- 如果 cpu 比較忙,那麼優先級高的線程會獲得更多的時間片,但 cpu 閑時,優先級幾乎沒作用
Reference
全面深入學習java并發程式設計