天天看點

Java并發程式設計:Java線程(一)

建立和運作線程

  • 方法一:直接使用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并發程式設計