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理解和使用总结
相关推荐
guoruijun_2012_41 小时前
hyperf 配置步骤
android
500了1 小时前
Android和Java的发布/订阅事件总线EventBus
android·java·开发语言
诸神黄昏EX1 小时前
Android 常用命令和工具解析之Trace相关
android
明天再做行么3 小时前
PHP8解析php技术10个新特性
android·php
Ting丶丶3 小时前
安卓应用安装过程学习
android·学习·安全·web安全·网络安全
kingdawin4 小时前
Android系统开发-判断相机是否在使用
android
恋猫de小郭5 小时前
IntelliJ IDEA 2024.3 K2 模式已发布稳定版,Android Studio Meerkat 预览也正式支持
android·android studio
找藉口是失败者的习惯9 小时前
Jetpack Compose 如何布局解析
android·xml·ui
Estar.Lee14 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
温辉_xh14 小时前
uiautomator案例
android