每个程序至少有一个线程 —— 主线程
主线程是程序的起点,你可以从它开始创建新的线程来执行任务。为此,你需要创建自定义线程,编写在线程中执行的代码,并启动它。
通过继承创建自定义线程
创建新线程有两种主要方式:
-
继承
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!
最终输出表明,两个线程并发执行。虽然不是真正同时,但每个线程都得到了执行的机会。
总结
-
如何创建线程对象;
-
如何设置线程执行的代码;
-
如何启动线程;
-
线程背后的工作原理;
-
线程运行的非确定性;
-
简单的多线程程序如何写。