一、Android Thread介绍
Android中的线程机制是通过Thread类和Handler类实现的。Thread类用于创建和管理线程,而Handler类用于在不同线程之间传递和处理消息。
在Android中,主线程(也称为UI线程)负责处理用户界面的更新和事件响应。为了避免在主线程中执行耗时操作导致界面卡顿,我们可以创建新的线程来执行这些操作。Android提供了Thread类来创建和管理线程。我们可以通过继承Thread类或实现Runnable接口来创建自定义的线程。
在创建线程后,我们可以使用Handler类来与主线程进行通信。Handler类允许我们将消息发送到主线程的消息队列中,并在主线程中处理这些消息。通过Handler类,我们可以在子线程中执行耗时操作,并将结果发送到主线程进行界面更新。
下面是一个简单的Android线程+消息的例子:
// 在主线程中创建Handler对象
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 在主线程中处理消息
switch (msg.what) {
case 1:
// 执行界面更新操作
break;
case 2:
// 执行其他操作
break;
default:
break;
}
}
};
// 在子线程中发送消息到主线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时操作
// 发送消息到主线程
handler.sendEmptyMessage(1);
}
});
thread.start();
在上面的例子中,我们在主线程中创建了一个Handler对象,并重写了handleMessage()方法来处理消息。然后,在子线程中执行耗时操作,并通过handler.sendEmptyMessage()方法发送消息到主线程。
通过Android的线程+消息机制,我们可以实现多线程编程,并在不同线程之间进行通信和协调,从而提高应用程序的性能和用户体验。
二、线程相关类和接口
JAVA类
Runable
Runnable是一个接口,它定义了一个run()方法,用于在另一个线程中执行代码。在Android中,Runnable通常用于在后台线程中执行耗时操作,以避免阻塞UI线程。
Runnable代码位于:
libcore/ojluni/src/main/java/java/lang/Runable.java
Runnable的定义:
public interface Runnable {}
Runnable API:
public abstract void run() :运行
Thread
Android Thread是Android系统为线程操作而封装的一个类,它封装了一些与线程同步相关的类,Mutex、Condition等。Android Thread类可以用于在Android应用程序中创建和管理线程,使得开发者可以更加方便地进行多线程编程。在Android中,主线程也称为UI线程,它负责处理用户界面的事件和更新UI界面,而子线程则用于执行耗时的任务,以避免阻塞主线程。如果在主线程中执行耗时任务,会导致UI界面无响应,甚至出现ANR(Application Not Responding)的情况。因此,开发者需要在子线程中执行耗时任务,以保证应用程序的流畅性和稳定性。
Thread代码位于:
libcore/ojluni/src/main/java/java/lang/Thread.java
Thread的定义:
public class Thread implements Runnable {}
Thread API:
public Thread() :构造方法
public Thread(Runnable target) :构造方法
public Thread(ThreadGroup group, Runnable target):构造方法
public Thread(String name):构造方法
public Thread(ThreadGroup group, String name):构造方法
public Thread(Runnable target, String name) :构造方法
public Thread(ThreadGroup group, Runnable target, String name) :构造方法
public Thread(ThreadGroup group, Runnable target, String name, long stackSize):构造方法
public Thread(ThreadGroup group, Runnable target, String name, long stackSize, boolean inheritThreadLocals) :构造方法
public synchronized void start() :启动线程
public void run() :线程运行时所执行的代码
public void interrupt() :中断线程,注意只能中断阻塞状态的线程
public final boolean isAlive() :获取状态
public final void setPriority(int newPriority):设置线程优先级
public final int getPriority() :获取线程优先级
public final synchronized void setName(String name) :设置线程的name
public final String getName() :获取线程的name
public final ThreadGroup getThreadGroup():获取线程的组
public static int activeCount():返回当前线程的线程组中活动线程的数量
public static int enumerate(Thread tarray[]):返回一个包含当前所有活动线程的列表
public final void join(long millis):线程同步方法,它会阻塞调用线程,直到目标线程执行完毕。
public final void join(long millis, int nanos):线程同步方法,它会阻塞调用线程,直到目标线程执行完毕。
public final void join():线程同步方法,它会阻塞调用线程,直到目标线程执行完毕。
public static void dumpStack():用于打印当前线程的堆栈跟踪信息
public final void setDaemon(boolean on):将线程设置为后台线程(守护线程)
public final boolean isDaemon():用于判断当前线程是否为守护线程
public final void checkAccess() :检查当前运行的线程是否有权限更新线程组
public static native Thread currentThread():返回对当前正在执行的线程对象的引用。
ThreadGroup
ThreadGroup是Android中的一个类,它用于将一组线程组合在一起。ThreadGroup可以帮助我们更好地管理线程,例如可以通过ThreadGroup来统一管理一组线程的优先级、中断等操作。ThreadGroup还可以帮助我们更好地处理线程异常,例如可以通过ThreadGroup来捕获一组线程中的异常并进行处理。
ThreadGroup代码位于:
libcore/ojluni/src/main/java/java/lang/ThreadGroup.java
ThreadGroup的定义:
public class ThreadGroup implements Thread.UncaughtExceptionHandler {}
ThreadGroup API:
public ThreadGroup(ThreadGroup parent, String name):构造方法
public final String getName() :返回现在组名称
public final ThreadGroup getParent() :返回父进程组。
public final int getMaxPriority() :返回最大有效级
public final boolean isDaemon():测试此线程组是否为守护程序线程组
public synchronized boolean isDestroyed() :测试此线程组是否已被销毁。
public final void setDaemon(boolean daemon):更改此线程组的守护程序状态。
public final void setMaxPriority(int pri) :设置最大优先级
public final boolean parentOf(ThreadGroup g) :测试此线程组是线程组参数还是其祖先线程组之一。
public final void checkAccess():确定当前正在运行的线程是否有权修改此线程组。
public int activeCount() :返回此线程组及其子组中活动线程数的估计值。递归循环访问此线程组中的所有子组。
public int enumerate(Thread list[]):将此线程组及其子组中的每个活动线程复制到指定的数组中。
public int enumerate(Thread list[], boolean recurse):将此线程组及其子组中的每个活动线程复制到指定的数组中。
public int activeGroupCount() :返回此线程组及其子组中活动组数的估计值。
public int enumerate(ThreadGroup list[]) :将对此线程组及其子组中的每个活动子组的引用复制到指定的数组中。
public int enumerate(ThreadGroup list[], boolean recurse) :将对此线程组及其子组中的每个活动子组的引用复制到指定的数组中。
public final void stop() :停止此线程组中的所有线程。
public final void interrupt():中断此线程组中的所有线程。
public final void suspend():挂起此线程组中的所有线程。
public final void resume():恢复此线程组中的所有线程。
public final void destroy():销毁此线程组及其所有子组。
ThreadLocal
ThreadLocal是Java中的一个线程本地变量,它为每个线程提供了一个独立的变量副本,使得每个线程都可以独立地改变自己的副本,而不会影响其他线程的副本。ThreadLocal通常用于解决多线程并发访问时的数据安全问题,例如在Web应用程序中,每个请求都会被分配到一个独立的线程中,如果多个请求同时访问同一个全局变量,就会出现数据安全问题,此时可以使用ThreadLocal来解决这个问题。
ThreadLocalMap是Java中的一个类,它是ThreadLocal类的内部类,用于实现线程本地变量。每个线程都有一个ThreadLocalMap对象,用于存储该线程的所有ThreadLocal变量。ThreadLocalMap是一个自定义的哈希表,它的键是ThreadLocal对象,值是对应的变量值。当我们调用ThreadLocal的get()方法时,它会返回当前线程的ThreadLocalMap中与该ThreadLocal对象对应的变量值。当我们调用ThreadLocal的set()方法时,它会将该ThreadLocal对象和对应的变量值存储到当前线程的ThreadLocalMap中。需要注意的是,由于ThreadLocalMap是线程本地的,因此不同线程之间的变量值是互相独立的,这样就实现了线程隔离。
ThreadLocal代码位于:
libcore/ojluni/src/main/java/java/lang/ThreadLocal.java
ThreadLocal的定义:
public class ThreadLocal<T> {
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {}
static class ThreadLocalMap {}
}
ThreadLocal API:
public T get():返回此线程局部变量的当前线程副本中的值。
boolean isPresent() :如果当前线程的副本中存在此线程局部变量的值,则返回 {@code true},即使该值为 {@code null}。
public void set(T value):将此线程局部变量的当前线程副本设置为指定值。
public void remove():删除此线程局部变量的当前线程值。
ThreadLocalWorkSource
跟踪谁触发了当前在此线程上执行的工作。ThreadLocalWorkSource 在系统服务器内部自动更新,用于传入/传出绑定器调用和发布到处理程序线程的消息。如果需要优化 WorkSource,也可以手动设置 ThreadLocalWorkSource。仅供在系统服务器内使用。
ThreadLocalWorkSource代码位于:
android/os/ThreadLocalWorkSource.java
ThreadLocalWorkSource的定义:
public final class ThreadLocalWorkSource {}
ThreadLocalWorkSource 方法:
public static int getUid():返回当前在此线程上执行的代码的 UID。
public static long setUid(int uid) :设置Uid。
public static void restore(long token):使用提供的令牌还原状态。
public static long clear():清除存储的工作源 uid。
HandlerThread
HandlerThread是Google帮我们封装好的,可以用来执行多个耗时操作,而不需要多次开启线程,里面是采用Handler和Looper实现的。HanderThread实际上就是一个线程。
HandlerThread代码位于:
framework/base/core/java/android/os/HandlerThread.java
HandlerThread的定义:
public class HandlerThread extends Thread {}
HandlerThread API:
public HandlerThread(String name):构造方法
public HandlerThread(String name, int priority):构造方法
public void run():运行
public Looper getLooper():此方法返回与此线程关联的 Looper
public Handler getThreadHandler() :返回与此线程关联的共享Handler
public boolean quit():退出处理程序线程的循环器。
public boolean quitSafely():安全地退出处理程序线程的 looper。
public int getThreadId():返回此线程的标识符。
C++类
Thread
Android libutils库中的Thread类是一个轻量级的线程封装类,用于在Android应用程序中创建和管理线程。它提供了一种简单的方式来执行并发任务,并且可以与其他Android组件(如Activity和Service)进行交互。
Thread代码位于:
system/core/libutils/Thread.cpp
system/core/libutils/include/utils/Thread.h
Thread的定义:
class Thread : virtual public RefBase {}
三、Thread流程分析
线程的启动
继承Thread类
继承Thread,重写run()方法。使用时直接new并且start()
class MyThread extends Thread {
@Override
public void run() {
//TODO...
}
}
// 调用使用线程
Thread myThread = new MyThread(); // 新建状态
myThread.start(); // 就绪状态
实现Runable接口
实现Runnable,重写run()方法来执行任务。
class MyRunnable implements Runnable {
@Override
public void run() {
//TODO...
}
}
// 调用使用线程
Runnable myRunnable = new MyRunnable(); // 创建Runable类对象
Thread thread = new Thread(myRunnable); // 将Runnable对象作为Thread target创建新的线程
thread.start();
另一种启动方式:
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
同时使用继承Thread的类和实现Runnable接口的类
Runnable myRunnable = new MyRunnable();
Thread myThread = new MyThread(myRunnable);
myThread.start();
这种方式也能创建一个新的线程。但是,此线程执行到底是MyRunnable接口中的run方法还是MyThread类中的run方法呢?答案是:MyThread类中的run()方法。因为Thread类本身也实现了Runnable接口,而重写Runnable的run方法的。
通过Handler启动线程
首先定义好Handler和Runnable :
private int count = 0;
private Handler mHandler = new Handler();
private Runnable runnable = new Runnable() {
@Override
public void run() {
Log.i("download",Thread.currentThread().getName()+":"+count);
count ++;
mHandler.postDelayed(runnable,1000); // 执行后延迟1000毫秒再次执行,count已++
}
};
然后使用mHandler的post()方法执行线程。当上方的Runnable执行后里面定义了mHandler.postDelayed(runnable,1000);开启延迟1000毫秒后再次执行。
findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler.post(runnable); // handler运行runnable
}
});
线程的终止
使用boolean变量作为标记
如下,如果变量stop为true则跳出run()方法。关键字volatile表示这个变量随时都有可能发生变化,主要是表示同步,也就是同一时刻只能由一个线程来修改该变量的值。
public class ThreadStopTest extends Thread {
public volatile boolean stop = false;
@Override
public void run() {
super.run();
while (!stop){
// thread runing
}
}
}
使用interrupt()
该方法可以用来中断进程,然后就可以终止线程,使用该方法分两种情况,线程正常运行状态和阻塞状态。
线程正常运行状态
要注意的是interrupt()方法只是中断线程而不是结束线程,在线程正常运行状态下使用该方法是不能结束线程的,正确的做法是判断线程是否被中断来决定是否执行代码。这种方法类似于自定义标记。看一下示例:
...
// 定义开始和结束线程的方法,与按钮绑定
public void goThread(){
if(null == myThread){
myThread = new MyThread();
}
myThread.start();
}
private void stopThread() {
if(null != myThread && myThread.isAlive()){
myThread.interrupt();
myThread = null;
}
}
public class MyThread extends Thread{
@Override
public void run() {
super.run();
int i = 0;
// 判断状态,如果被打断则跳出并将线程置空
while (!isInterrupted()){
i++;
Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
}
}
}
线程阻塞状态
先看下面的例子,重新写了一个SleepThread,开启和停止的代码省略。这个线程里每间隔1s循环增加i的值并打印。
public class SleepThread extends Thread{
@Override
public void run() {
super.run();
int i = 0;
while(true){
try {
i++;
Thread.sleep(1000);
Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
} catch (InterruptedException e) {
e.printStackTrace();
Log.i("thread",Thread.currentThread().getName()+"异常抛出,停止线程");
break;
}
}
}
}
可以看到调用interrupt()方法后抛出InterruptedException异常,同时break跳出循环达到停止跑代码的作用。原理是当线程进入阻塞状态时,调用interrupt()方法会抛出异常,利用这个异常来跳出循环。
两种状态一起处理
此外,把两种状态的处理结合在一起是比较好的,这样既可以及时判断线程状态又可以捕获异常来跳出循环。
public class SleepThread extends Thread{
@Override
public void run() {
super.run();
int i = 0;
while(!isInterrupted()){ // 判断线程是否被打断
try {
i++;
Thread.sleep(1000);
Log.i("thread",Thread.currentThread().getName()+":Running()_Count:"+i);
} catch (InterruptedException e) {
e.printStackTrace();
Log.i("thread",Thread.currentThread().getName()+"异常抛出,停止线程");
break;// 抛出异常跳出循环
}
}
}
}
线程的休眠(sleep)
使用sleep()方法时,必须加入InterruptedException异常捕捉,使用sleep()方法的会使线程进入睡觉状态,也就是上面提到的阻塞状态,接收的参数是毫秒,在设定的毫秒时间内醒来后,它并不能保证能进入运行状态,只能保证它进入就绪状态。使用如下:
……
try {
Thread.sleep(2000);
} catch (InterrupedException e) {
e.printStackTrace();
}
……
线程的加入(Join)
Thread.Join()是一个线程同步方法,它允许一个线程等待另一个线程完成其执行。当一个线程调用Join()方法时,它将被阻塞,直到目标线程完成其执行。这个方法通常用于在主线程中等待所有子线程完成其执行,以确保所有线程都已完成后再继续执行主线程的代码。
以下是一个简单的示例,演示了如何使用Thread.Join()方法等待一个线程完成其执行:
using System;
using System.Threading;
public class Example
{
public static void Main()
{
Thread t = new Thread(DoWork);
t.Start();
// 等待线程t完成其执行
t.Join();
Console.WriteLine("All done!");
}
public static void DoWork()
{
Console.WriteLine("Working...");
Thread.Sleep(5000); // 模拟长时间运行的操作
Console.WriteLine("Work complete.");
}
}
四、Android线程安全与线程同步
简单地说,线程安全问题是指多个线程访问同一代码或数据,造成结果和数据的错乱或与期望的结果不同所产生的问题。
基本上所有的并发模式在解决线程安全问题的问题上,都采用“序列化访问临界资源”的方案,即在同一时刻只能有一个线程访问临界资源(多个线程可能同时访问的数据或资源),也称同步互斥访问。
synchronized
synchronized 关键字,保证同时刻只有一个线程进入该方法或者代码块,使用方式:
线程run()方法中要执行的代码方法添加synchronized关键字,注意要添加在方法的返回值前。
int count = 100;
private synchronized void count() {
if (count > 0) {
Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
} else {
isRunning = false;
}
}
同步代码块的形式使用。
private void count() {
synchronized (this) {
if (count > 0) {
Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
} else {
isRunning = false;
}
}
}
重入锁
使用重入锁实现线程同步。
-
ReentrantLock() : 创建一个ReentrantLock实例
-
lock() :获得锁
-
unlock() : 释放锁
private void count() {
lock.lock();
if (count > 0) {
Log.e(TAG, Thread.currentThread().getName() + "--->" + count--);
} else {
isRunning = false;
}
lock.unlock();
}
ThreadLocal
ThreadLocal管理变量。如果一个变量使用ThreadLocal进行管理,每一个使用该变量的线程都会获得该变量的副本,副本之间相互独立,所以每个线程都可以修改变量而不会对其它线程造成影响。
private static ThreadLocal<Integer> number = new ThreadLocal<Integer>(){
// 重写方法,设置默认值
@Override
protected Integer initialValue() {
return 1;
}
// 自定义方法设置变量值
public void saveNumber(int newNumber){
number.set(number.get() + newNumber);
}
// 自定义方法获取变量值
public int getNumber(){
return number.get();
}
};
五、Thread流程分析
Thread start流程分析
Android13 Thread start流程分析-CSDN博客
Thread run流程分析
Android13 Thread run流程分析-CSDN博客
HandlerThread 创建流程
Android13 HandlerThread 创建流程-CSDN博客