android 线程和线程池

yinbaoshiguang 2019-07-01

线程

一、简介

线程是CPU调度的最小单位。当一个程序第一次启动的时候,Android 会启动一个 Linux 进程和一个主线程。Android 中所有的组件都在主线程中实例化。主线程主要负责处理 UI 相关的事件,所以又被叫做 UI 线程。在 Android 中主线程是不能够做耗时操作的,子线程是不能够更新UI的。
android 线程和线程池
与进程的区别:
android 线程和线程池

二、创建多线程

2.1 继承 Thread 类

优点:实现简单,只要继承 Thread 类,并重写 run 方法就可以实现多线程。

缺点

  • 不适合资源共享。一个线程等于一个实例,相对独立,没办法进行资源共享;
  • 消耗资源。Thread 线程相当于一次性消耗品,一个线程等于一个耗时任务,1个耗时任务执行完毕,线程会自动销毁。有100个耗时任务,就需要开启100个线程。多次创建和销毁十分消耗系统资源。

具体使用

// 步骤1:创建线程类 (继承自Thread类)
class MyThread extends Thread{
    @Override
    public void run(){
        // 步骤2:从写run方法
    }
}
// 步骤3:创建线程对象,即 实例化线程类
MyThread mt=new MyThread(“线程名称”);
// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
mt.start();
可复用,代码量大

使用匿名类

new Thread("线程名称") {
    @Override
    public void run() {
        // doSomeThing          
    }.start();
不可复用,简介

注意:Thread 调用 run() 方法,就是一个普通的方法,失去线程的特性;start() 方法才会启动一个线程。

2.2 实现Runnable接口

优点

  • 适合资源共享。Runnable 的代码可以被多个线程(Thread 实例)共享,适合多个线程共同处理同一资源的情况;
  • 灵活。一个类可以继承多个接口,避免了继承 Thread 的单继承局限性。
Java中真正能创建新线程的只有Thread类对象。通过实现Runnable的方式,最终还是通过Thread类对象来创建线程。所以对于实现了Runnable接口的类,称为线程辅助类;Thread类才是真正的线程类。

具体使用

// 步骤1:创建线程辅助类,实现Runnable接口
 class MyRunnable implements Runnable{
    @Override
    // 步骤2:复写run(),定义线程行为
    public void run(){

    }
}
// 步骤3:创建线程辅助对象,即 实例化 线程辅助类
MyRunnable mr=new MyRunnable();
// 步骤4:创建线程对象,即 实例化线程类;线程类 = Thread类;创建时通过Thread类的构造函数传入线程辅助类对象
Thread td=new Thread(mr);
// 步骤5:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
td.start();

使用匿名类

Runnable mt = new Runnable() {
    @Override
    public void run() {
    
    }
};
Thread mt1 = new Thread(mt, "窗口1");       
mt1.start();

2.3 AsyncTask

作用
实现工作线程 & 主线程(UI线程)之间的通信,即:将工作线程的执行结果传递给主线程,从而在主线程中执行相关的UI操作。从而保证线程安全。
优点

  • 方便实现异步通信。不需使用 “任务线程(如继承Thread类) + Handler”的复杂组合;
  • 节省资源。采用线程池的缓存线程 + 复用线程,避免了频繁创建 & 销毁线程所带来的系统资源开销。

缺点

  • 线程是串行执行的(即后台只有一个线程);
虽然 AsyncTask 的任务执行是通过线程池,线程池的核心线程,非核心线程规格大于1,但是后台运行的线程也只有一个,这是因为 AsyncTask 的任务管理线程池是 串行的 即,执行完一个任务才会执行下一个任务。
  • 存在内存泄漏的风险。

类定义

public abstract class AsyncTask<Params, Progress, Result> { 
 ... 
}

// 类中参数为3种泛型类型
// 整体作用:控制AsyncTask子类执行线程任务时各个阶段的返回类型
// 具体说明:
    // a. Params:开始异步任务执行时传入的参数类型,对应excute()中传递的参数
    // b. Progress:异步任务执行过程中,返回下载进度值的类型
    // c. Result:异步任务执行完成后,返回的结果类型,与doInBackground()的返回值类型保持一致
// 注:
    // a. 使用时并不是所有类型都被使用
    // b. 若无被使用,可用java.lang.Void类型代替
    // c. 若有不同业务,需额外再写1个AsyncTask的子类
}

AsyncTask 核心 & 常用的方法如下
android 线程和线程池

使用步骤

  • 创建 AsyncTask 子类 & 根据需求实现核心方法
  • 创建 AsyncTask子类的实例对象(即 任务实例)
  • 手动调用execute(()从而执行异步线程任务。同一个AsyncTask实例对象只能执行1次,若执行第2次将会抛出异常

注意

  1. AsyncTask 是基于线程池进行实现的,当一个线程没有结束时,后面的线程是不能执行的。同时,AsyncTask 不与任何组件绑定生命周期,在Activity 或 Fragment中使用 AsyncTask时,最好在Activity 或 Fragment的 onDestory 方法里面调用 cancel(boolean);
if (myAsyncTask != null && myAsyncTask.getStatus() == Status.RUNNING) {
    //cancel方法只是将对应的AsyncTask标记为cancelt状态,并不是真正的取消线程的执行.
    myAsyncTask.cancel(true);
}
  1. 若 AsyncTask 被声明为 Activity 的非静态内部类,当 Activity 需销毁时,会因 AsyncTask 保留对 Activity 的引用而导致 Activity 无法被回收,最终引起内存泄露。AsyncTask 应被声明为静态内部类;
  2. 当 Activity 重新创建时(屏幕旋转 / Activity被意外销毁时后恢复),之前运行的 AsyncTask(非静态的内部类)持有的之前Activity 引用已无效,故复写的 onPostExecute() 将不生效,即无法更新UI操作。所以,在 Activity 恢复时的对应方法需要重启任务线程。
  3. cancel 方法被调用后,onPostExecute和onProgressUpdate方法都不会再调用了。而doInBackground方法却会一直执行下去,也就是后台任务会继续执行。所以,cancel 并不是真正的取消,只是改变了标志状态,断开了和主线程之间的联系。真正的取消需要在doInBackground里面操作。
  4. 调用 execute 启动线程是串行的(1任务执行完才会执行2任务...)。那如何让任务并行执行呢,如下:
// AsyncTask 自带的线程池
task1.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
task2.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// MY_THREAD_POOL_EXECUTOR 为自定义的线程池
task1.executeOnExecutor(MY_THREAD_POOL_EXECUTOR);
task2.executeOnExecutor(MY_THREAD_POOL_EXECUTOR);
// 替换掉默认的 AsyncTask.SERIAL_EXECUTOR
MyAsyncTask.setDefaultExecutor(MY_THREAD_POOL_EXECUTOR);
new MyAsyncTask(mActivity, "Task#a ").execute("abc");
new MyAsyncTask(mActivity, "Task#b ").execute("abc");

demo连接:处理了AsyncTask串行、并行问题,自定义线程池,以及AsyncTask内存泄漏的问题

原理
android 线程和线程池
android 线程和线程池

参考原文:Android 多线程:AsyncTask的原理 及其源码分析

线程池

Carson_Ho的原文地址:Android多线程:线程池ThreadPool 全面解析

相关推荐