Android中的Thread详解

一、前言

线程(英语: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:完成或者异常,终止运行

1. 正常线程执行状态:

graph LR new(new) runnable(runnable) running(running) terminated(terminated) new --> runnable ---->|被CPU调用中| running --> terminated

2. waiting状态线程:

graph LR new(new) runnable(runnable) running(running) waiting(waiting) terminated(terminated) new --> runnable -->|被CPU调用中| running --> terminated running -.->|"object.wait() or object.join()"| waiting -.->|"object.notify() or object.notifyAll()" | runnable

3. blocked状态:

graph LR new(new) runnable(runnable) running(running) blocked(blocked) terminated(terminated) new --> runnable -->|被CPU调用中| running --> terminated running -.->|"等待同步锁状态"| blocked -.->|"拿到同步锁" | runnable

4. timed_waiting状态:

graph LR new(new) runnable(runnable) running(running) timed_waiting(timed_waiting) terminated(terminated) new --> runnable -->|被CPU调用中| running --> terminated running -.->|"sleep(*)或者wait(*)或者join(*)"| timed_waiting -.->|"时间到或者notify()或者notifyAll()"| runnable

六、文档链接

  1. java锁之wait,notify(wait会释放锁,notify仅仅只是通知,不释放锁)
  2. Android 多线程:Thread理解和使用总结
相关推荐
阿巴斯甜3 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker4 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95275 小时前
Andorid Google 登录接入文档
android
黄林晴6 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab19 小时前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿21 小时前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android