近來讀了好幾篇“老馬說編程”寫的 Java 多線程文章,這里記錄一下第一篇的摘要,以及拓展查找了點相關的東西,算是復習鞏固(有些冷知識平時還真沒留意過)。原文:(65) 線程的基本概念 / 計算機程序的思維邏輯
在 Java 中創建線程有兩種方式,一種是繼承 Thread,另外一種是實現 Runnable 接口。
extends Thread
-
繼承 Thread 的類,重載 run 方法,方法簽名是固定的,public,沒有參數,沒有返回值,不能拋出 checked Exception (可以拋出unchecked Exception),下圖幫大家復習一下兩種 Exception
checked & unchecked Exception 調用 Thread 的 start 方法后,線程開始執行;如果不調用 start,run 的代碼會在當前線程執行
怎么確認在跑的那一個線程呢?
/**
* Returns a reference to the currently executing thread object.
* @return the currently executing thread.
*/
public static native Thread currentThread();
難得看到 native 關鍵字,所以 Thread 是 JNI (Java Native Interface),略懵逼,哪位高人來指導下?
- 線程 id 是遞增的整數,線程name可以設置
/* For generating thread ID */
private static long threadSeqNumber;
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
implements Runnable
- 僅僅實現 Runnable 是不夠的,需要new Thread,啟動線程都是調用 Thread 對象的 start 方法
程序什么時候退出,關于 daemon 線程
- 啟動線程會啟動一條單獨的執行流,整個程序只有在所有線程都結束的時候才退出
- 但 daemon 線程是例外,當整個程序中剩下的都是 daemon 線程的時候,程序就會退出。
- daemon 線程一般是其他線程的輔助線程,在它輔助的主線程退出的時候,它就沒有存在的意義了。
- Java 也會創建多個線程,除了 main 線程外,至少還有一個負責垃圾回收的線程,這個線程就是 daemon 線程,在 main 線程結束的時候,垃圾回收線程也會退出。
關于 Thread 的小知識
- sleep():睡眠期間,該線程會讓出CPU,但睡眠的時間不一定是確切的給定毫秒數,可能有一定的偏差。
- yield():告訴操作系統的調度器,我現在不著急占用CPU,你可以先讓其他線程運行。(想起 Python 的 yield ??)
- ** join()**:讓調用join的線程等待該線程結束,比如希望main線程在子線程結束后再退出
public static void main(String[] args) throws InterruptedException {
Thread thread = new HelloThread();
thread.start();
thread.join();
}
- 已經 deprecated 的方法
- public final void stop()
- public final void suspend()
- public final void resume()
stop() 會引起線程涉及的 monitor 狀態的不確定性,所以不建議被調用,那么怎么 stop 一個 Thread 啊,懵逼了吧。
- 怎么合理的 stop 一個線程呢?
參考 StackOverflow 的帖子 (Thread.stop() - deprecated)
The Answer: In Java there's no clean, quick or reliable way to stop a thread.
Instead, Threads rely on a cooperative mechanism called Interruption. This means that Threads could only signal other threads to stop, not force them to stop.
- 一種辦法是通過一個簡單的 flag 變量來控制
public class Task implements Runnable {
private volatile boolean isRunning = true;
public void run() {
while (isRunning) {
//do work
}
}
public void kill() {
isRunning = false;
}
}
- 類似還有通過 interrupt 來中斷線程
public class Task1 implements Runnable {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
// do work
}
}
}
// in other thread, call interrupt()
myThread.interrupt();
如果run里面不判斷 Thread.currentThread().isInterrupted() 直接執行 interrupt() 會怎樣呢?哪位讀者來回答一下 _
多線程的共享變量
不同執行流可以訪問和操作相同的變量
不同執行流可以執行相同的程序代碼
當多條執行流執行相同的程序代碼時,每條執行流都有單獨的棧,方法中的參數和局部變量都有自己的一份
[內存可見性] 多個線程可以共享訪問和操作相同的變量,但一個線程對一個共享變量的修改,另一個線程不一定馬上就能看到,甚至永遠也看不到。原因:在計算機系統中,除了內存,數據還會被緩存在CPU的寄存器以及各級緩存中,當訪問一個變量時,可能直接從寄存器或CPU緩存中獲取,而不一定到內存中去取,當修改一個變量時,也可能是先寫到緩存中,而稍后才會同步更新到內存中。
多線程的成本
創建開銷(空間和時間):每個線程創建必要的數據結構、棧、程序計數器。
線程切換開銷(時間):上下文切換,這個切換不僅耗時,而且使CPU中的很多緩存失效,是有成本的。
如果執行的任務都是CPU密集型的,那創建超過CPU數量的線程是沒有必要的,并不會加快程序的執行。
IO密集型的,創建超過 CPU 核數的線程是會提高效率的(線程數到多少合適呢?Netty的源碼里有相關的解說)