一、前言
线程(英语:thread)在计算机科学中,是将进程划分为两个或多个线程(实例)或子进程,由单处理器(单线程)或多处理器(多线程)或多核处理系统并发执行。 ------ 维基百科
无论在Android还是java开发甚至其他语言开发,大多数都逃不过多线程的话题,当然dart等语言除外(不过也有isolate的概念)。
线程是操作系统CPU资源分配调度的一个单元,在Java中,最常见的就是Thread类,通过该类可以让我们在多线程创建、启动、执行等操作更加得心应手。
二、了解
- 在Thread类中,只有run函数是运行在线程上。
- 通过start()启动线程,实际上会调用native的VMThread.create(this, stackSize)方法创建CPU线程
三、使用方法
1.通过继承Thread启动一个线程
继承Thread,并复写run方法即可
kotlin
class CustomThread : Thread() {
override fun run() {
//执行一些耗时操作
for(i in 0..1000){
println("work $i")
}
}
}
fun main() {
val customThread = CustomThread()
customThread.start()
}
2.通过构造函数传入
直接通过构造函数,传入自定义的Runnable接口
kotlin
fun main() {
val thread = Thread({
//执行一些耗时操作
for (i in 0..1000) {
println("work $i")
}
}, "CustomThread")
thread.start()
}
四、详解
1.启动流程
thread.start()是线程启动的入口,只有在start执行时真正创建了native线程,其他准备工作(比如设置name、runnable等)都是在java的当前线程执行处理。
这里Android进行了改造,将start0改成了nativeCreate开启线程。
注意这里做了实例的同步锁。
源码:
java
public synchronized void start() {
// 禁止多次调用start()方法,重复开始抛异常
if (started)
throw new IllegalThreadStateException();
group.add(this);
// STUDY: 2023/11/11 保证字段正确性
started = false;
try {
// Android的改造
// Android-changed: Use Android specific nativeCreate() method to create/start thread.
// start0();
nativeCreate(this, stackSize, daemon);
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
java
private native static void nativeCreate(Thread t, long stackSize, boolean daemon);
2.run流程
主要是执行传入的runnable接口回调。只有在runnable中执行的才是在新线程,其他的都是在当前线程。
由start执行后,由JVM自动调用run方法在新线程中。
源码:
java
// aosp12-Thread.java
private Runnable target;
...
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc){
...
this.target = target
...
}
...
@Override
public void run() {
if (target != null) {
target.run();
}
}
3.wait
wait()为Object的公有方法,使用时需要配合synchronized和Object实例调用,用来阻塞当前线程。此时释放掉synchronized的锁。如果被唤醒也是需要"随机"竞争锁。
- 在哪个线程执行,就会阻塞那个线程
- 必须在synchronized同步代码块中调用
- wait时会释放掉同步锁
- 仅释放调用wait方法的锁,不会改变其他锁的状态
- 会有InterruptedException异常
参数:
- wait():内部调用wait(0),表示无超时时间,一直等待
- wait(long timeout):内部调用wait(timeout, 0)
- wait(long timeout, int nanos):native方法,进入等待状态,如果是0为一直等待,直到收到唤醒通知
举例:
kotlin
val lock = Object()
...
private fun sendWait(){
synchronized(lock){
try {
// 阻塞当前线程,但会释放调用的lock的锁
lock.wait()
} catch (e :InterruptedException) {
e.printStackTrace()
}
}
}
4.notify、notifyAll
notify()、notifyAll()为Object的公有方法,使用时需要配合synchronized和Object实例调用,用来通知唤醒阻塞线程。notify不会有释放锁操作,仅仅通知wait状态的线程可以去竞争锁,而不是立马拿到,需要等到notify代码块执行完毕释放锁。notify为随机唤醒一个阻塞线程,notifyAll为唤醒所有阻塞线程。
- notify操作不会有释放锁操作,仅作为通知
- notify为随机通知一个wait唤起
- notifyAll通知所有wait状态唤起
- 会有InterruptedException异常
举例:
kotlin
val lock = Object()
...
private fun sendNotify(){
synchronized(lock){
try {
// 随机通知一个阻塞唤醒
lock.notify()
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
kotlin
val lock = Object()
...
private fun sendNotifyAll(){
synchronized(lock){
try {
// 通知所有阻塞唤醒,但也要竞争锁
lock.notifyAll()
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
}
5.sleep
sleep()为Thread的静态方法。使当前线程睡眠指定一段时间,之后自行恢复,notify对此无用。此时会持有thread的lock对象锁,线程休眠也不会释放其他锁。
- 在哪个线程执行,就会阻塞那个线程。
- 睡眠期间不会释放所有锁
- 会有InterruptedException异常
参数:
- sleep(long millis):调用sleep(millis, 0)
- sleep(long millis, int nanos):线程睡眠
案例:
kotlin
private fun sendSleep(){
try {
// 这里会阻塞3s中,如果期间中断会被捕获异常
Thread.sleep(3000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
源码:
java
// aosp12-Thread.java
public static void sleep(long millis, int nanos)
throws InterruptedException {
...
// 如果无需睡眠,不做任何处理,中断状态下会抛出异常。
if (millis == 0 && nanos == 0) {
if (Thread.interrupted()) {
throw new InterruptedException();
}
return;
}
...
// 获取当前线程的Object锁对象
Object lock = currentThread().lock;
// sleep 此时一直持有锁,直到睡眠结束
synchronized (lock) {
// 循环保证睡眠时间执行完全
for (long elapsed = 0L; elapsed < durationNanos;elapsed = System.nanoTime() - startNanos) {
...
// 私有方法
sleep(lock, millis, nanos);
}
}
}
java
@FastNative
private static native void sleep(Object lock, long millis, int nanos) throws InterruptedException;
6.interrupt
interrupt为Thread的公有方法。线程中断后,可通过isInterrupted()进行判断是否处理中断状态。
- 未start()的线程,调用interrupt不影响,后续可正常操作
- 要在中断前执行特殊操作,需要设置 blockedOn(Interruptible b)
- 仅仅是打上中断标志,而非立刻停止。
需要以下做中断try catch,否则造成程序crash。
- Thread.sleep()
- object.wait()
- object.notify()
- object.notifyAll()
- thread.join()
举例:
kotlin
val thread: Thread? = null
...
fun sendInterrupt(){
thread?.interrupt()
}
源码:
java
// aosp12-Thread.java
public void interrupt() {
// 如果不是当前线程,进行判断
if (this != Thread.currentThread())
// Android相关移除了非同线程的操作异常,空方法
checkAccess();
// 通过blockerLock同步锁进行中断操作,防止设置不同步
synchronized (blockerLock) {
// 由blockedOn设置
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
// 中断前的回调操作
b.interrupt(this);
return;
}
}
// native层标记,而非立即中断
interrupt0();
}
java
// aosp12-Thread.java
// 缓存set的中断前回调
private volatile Interruptible blocker;
// 设置和执行blocker的同步锁
private final Object blockerLock = new Object();、
...
/** @hide */
// 设置中断回调
public void blockedOn(Interruptible b) {
synchronized (blockerLock) {
blocker = b;
}
}
7.join
join()是Thread的公有方法,等待线程任务的完成。可重复执行,如果线程销毁了,相当于直接完成。这里会阻塞当前执行线程,对于Android来说,千万不要再主线程去调用防止造成ANR。
参数:
- join():内部调用了join(0),无限制等待。直到任务完成
- join(long millis, int nanos):内部计算处理完具体时间后,调用join(long millis)
- join(long millis):最终调用方法,如果为0,无限时间等待;否则设置对应时间等待,到时自动结束
案例:
kotlin
val thread: Thread? = null
val TAG: String = "Thread"
...
fun sendJoin(){
try {
//阻塞当前线程,等待任务完成
thread?.join()
Log.d(TAG, "thread end, join finish")
} catch (e: InterruptedException){
e.printStackTrace()
}
}
源码:
java
// aosp12-Thread.java
public final void join(long millis)
throws InterruptedException {
// 使用lock同步锁进行阻塞
synchronized(lock) {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
// 无限时间等待。实际完成后cpp代码有调用notify相关唤醒这里
lock.wait(0);
}
} else {
while (isAlive()) {
// 现在时间小于预期时间,直接跳出循环,结束方法体
long delay = millis - now;
if (delay <= 0) {
break;
}
// 有限时间等待,超时会自动唤醒跳出。实际cpp代码有调用notify相关唤醒这里
lock.wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
}
8.yield
让步调度,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)。同样不会释放锁
五、状态图解
线程状态有5种:
- new: 实例化之后状态,没有start
- runnable:start()之后启动,等待cpu调度执行,非立刻执行
- blocked:阻塞状态,比如等锁
- waiting:无限时间等待状态
- timed_waiting:有限时间等待状态
- terminated:完成或者异常,终止运行