每個程序至少有一個線程 —— 主線程
主線程是程序的起點,你可以從它開始創建新的線程來執行任務。為此,你需要創建自定義線程,編寫在線程中執行的代碼,并啟動它。
通過繼承創建自定義線程
創建新線程有兩種主要方式:
-
繼承
Thread
類并重寫其run
方法; -
實現
Runnable
接口,并將實現類傳遞給Thread
類的構造函數。
下面是繼承 Thread
類并重寫 run
方法的示例:
class HelloThread : Thread() {override fun run() {val helloMsg = "Hello, i'm $name"println(helloMsg)}
}
而下面是實現 Runnable
接口并傳遞給 Thread
構造函數的示例:
class HelloRunnable : Runnable {override fun run() {val threadName = Thread.currentThread().nameval helloMsg = "Hello, i'm $threadName"println(helloMsg)}
}
這兩種方式都需要你重寫 run
方法,它是一個普通的方法,包含了你要執行的代碼。選擇哪種方式取決于具體任務和個人偏好。
如果你繼承了
Thread
類,可以直接使用其字段和方法,但 Kotlin 不支持多重繼承,所以你不能再繼承其他類。
創建線程對象
Thread
類有很多構造函數,我們來看其中幾個示例:
val t1 = HelloThread() // 使用子類
val t2 = Thread(HelloRunnable()) // 傳入 Runnable 實現
val myThread = Thread(HelloRunnable(), "my-thread") // 指定線程名
如果你想在 HelloThread
類中設置線程名,需要重寫構造函數。
因此,實現 Runnable
接口是一種更靈活的方式。
使用 Lambda 更簡單
如果你已經熟悉 Lambda 表達式,可以用下面這種方式:
val t3 = Thread {println("Hello, i'm ${Thread.currentThread().name}")
}
更簡便的線程創建方式
如果不想繼承或實現接口,也可以使用 thread(...)
函數創建線程,它來自 kotlin.concurrent
包:
import kotlin.concurrent.threadval t4 = thread(start = false, name = "Thread 4", block = {println("Hello, I'm ${Thread.currentThread().name}")
})
該函數的參數包括:
-
start
:是否立即啟動線程; -
isDaemon
:是否為守護線程; -
contextClassLoader
:類加載器; -
name
:線程名稱; -
priority
:優先級; -
block
:線程執行代碼塊。
啟動線程
使用 Thread
類的 start()
方法啟動線程。調用后,run
方法會在新線程中自動執行,但不會立刻執行。
示例:
fun main() {val t = thread(start = false, block = {println("Hello, I'm ${Thread.currentThread().name}")})t.start()
}
或者直接設置 start = true
(默認值):
fun main() {val t = thread(block = {println("Hello, I'm ${Thread.currentThread().name}")})
}
輸出示例:
Hello, i'm Thread-0
啟動線程的內部原理
線程啟動不會立即執行,啟動與運行之間存在延遲。線程默認是非守護線程。
-
守護線程不會阻止 JVM 退出;
-
非守護線程還在運行時,JVM 不會退出;
-
守護線程通常用于后臺任務。
注意:
-
start()
會啟動新線程并執行run()
方法; -
直接調用
run()
不會啟動新線程,只是在當前線程中執行; -
多次調用
start()
會拋出IllegalThreadStateException
; -
多線程中的執行順序是不可預測的,除非使用了額外的同步機制。
示例代碼:
fun main() {val t1 = HelloThread()val t2 = HelloThread()t1.start()t2.start()println("Finished")
}
可能的輸出順序:
Hello, i'm Thread-1
Finished
Hello, i'm Thread-0
也可能是:
Finished
Hello, i'm Thread-0
Hello, i'm Thread-1
說明線程的啟動順序與實際運行順序可能不同。
一個簡單的多線程程序
下面是一個簡單的多線程示例:
一個線程不斷讀取用戶輸入并打印其平方,主線程則間歇性輸出消息。
工作線程類:
class SquareWorkerThread(name: String) : Thread(name) {override fun run() {while (true) {val number = readln().toInt()if (number == 0) {break}println(number * number)}println("$name's finished")}
}
主線程邏輯:
fun main() {val workerThread = SquareWorkerThread("square-worker")workerThread.start()for (i in 0 until 5_555_555_543L) {if (i % 1_000_000_000 == 0L) {println("Hello from the main!")}}
}
示例輸入與輸出(帶注釋):
Hello from the main! // 主線程輸出
2 // 輸入
4 // 輸出平方
Hello from the main! // 主線程輸出
3
9
5
Hello from the main!
25
0 // 輸入 0 終止線程
square-worker finished // 工作線程結束提示
Hello from the main!
Hello from the main!
最終輸出表明,兩個線程并發執行。雖然不是真正同時,但每個線程都得到了執行的機會。
總結
-
如何創建線程對象;
-
如何設置線程執行的代碼;
-
如何啟動線程;
-
線程背后的工作原理;
-
線程運行的非確定性;
-
簡單的多線程程序如何寫。