[转]深入理解在Android中线程池的使用

简介: 这是一篇 写的非常用心的 博文,这里是原文地址https://blog.csdn.net/l540675759/article/details/62230562前言(1)本文共花费2周零3天的凌晨时光,这段时间收获很多.

这是一篇 写的非常用心的 博文,这里是原文地址
https://blog.csdn.net/l540675759/article/details/62230562

前言

(1)本文共花费2周零3天的凌晨时光,这段时间收获很多.

(2)从整理文章,作者从线程-->阻塞队列-->二进制-->线程池的内部机制,一路走来,本来是想写一篇为AsyncTask做铺垫的文章,没想到越写越多.

(3)文章中如果错误,请大家及时指正,作者会及时更新.

(4)希望大家能够从文章中.多多收获,迄今为止,博主最好的一篇文章,也是花了大力气最用心的一篇文章.

线程

在了解线程池之前,先给大家介绍下线程的概念

img_d866334f706f75733fa623ab02359983.png
image.png

先看一个烧水的例子,图中看电视是主线,用户想在看电视的过程中去完成烧水这个操作,并且不耽误看电视,看了这张图,在去了解接下来的概念会更好的理解主线程与子线程的概念。

线程是什么?

从底层角度来说:
一个线程就是在进程中的一个单一的顺序控制流.

而单个进程可以拥有多个并发执行的任务,每个任务都好像有自己的CPU一样,而其底层的机制就是切分CPU的时间,也就是CPU将轮流给每个任务分配其占用时间。

每个任务都觉得自己在一直占用CPU,而事实上是将CPU时间划分成片段分配给所有的任务。

在多个CPU的环境下,多线程的运作,可以极大的提供程序的运行速度,这就是线程存在的意义。


那么在Android中,线程的作用是?

首先,先了解下Android下进程和线程的概念:
这里引用Gityuan作者在知乎上的回答,关于线程和进程的概念

进程:每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。
进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。

线程:线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在CPU看来进程或线程无非就是一段可执行的代码,CPU采用CFS调度算法,保证每个task都尽可能公平的享有CPU时间片。


上面可能还是比较专业,这里简要总结下线程在Android的作用:

(1)在Android中线程分主线程和子线程,主线程也被称为UI线程,用来处理各种和界面相关的事情,
例 :界面的加载,Activity的生命周期这些都在主线程的范畴之内。

(2)由于主线程比较特殊,因为本身主线程在处理界面上,用了大部分的消耗,所以主线程不能再处理过于耗时的操作(IO操作,网络请求,大量的数据操作),否则就会造成ANR现象(程序卡死)。

什么是ANR?,这里百度上有比较全的介绍

而造成这种现象的主要原因有:

Activity响应时间超过5s

Broadcast在处理时间超过10s

Service处理时间超过20s

这大部分的原因是主线程进行过于耗时的操作,因为Activity,Broadcast,Serivce本身都是通过主线程进行承载的。

(3)此时子线程就横空出世解决了这类问题,Android建议耗时操作必须放在子线程中运行。

(4)而在Android中可以解决耗时问题的角色除了Thread之外还有AsyncTask,HandlerThread,IntentService,都可以实现此类功能,而他们的本质还是传统的线程。

img_5db62980fc3509be58db20201393405f.png
image.png

为什么会有线程池?

从字面上来看,线程池是存放,和管理线程的池子。那么为什么会有线程池呢?

先看一个例子,这里我用Handler和Thread来模拟网络请求的操作:

    private Handler mHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == TASK_ACTION) {
                Log.d("收到消息", "更新UI");
            }
            return false;
        }
    });
  new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模拟网络请求
                    Thread.sleep(1000);
                    mHandler.sendEmptyMessage(TASK_ACTION);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

上面过程,只是用一个Thread来模拟正常的网络请求,然后通过Handler来回调给UI线程,通知UI线程来刷新,如果对Handler机制不太了解,

一篇不错的Handler介绍的文章

上面只是单纯的一个网络请求,那么现在需求来了,这个界面不止一个网络请求,可能存在大量的网络请求,这时候就会有问题产生:

(1)当大量的网络请求产生,就会大量的创建和销毁线程,因此可能会造成过大的性能开销。

(2)当大量的线程一起运作的时候,可能会造成资源紧张,上面也介绍过线程底层的机制就是切分CPU的时间,而大量的线程同时存在时可能造成互相抢占资源的现象发生,从而导致阻塞的现象。

基于以上背景,线程池适当的出现可以很好的解决上述的问题,而上述模拟网络请求也只是一个简单的例子,而现实情况下,会有好多种情况和上述相似,比如在数据库操作大数据,多线程下载,在使用Thread的同时都会出现上述情况。


什么是线程池?

Android中的线程池的概念来源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor,ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池。

线程池的优点:

线程池的出现,恰恰就是解决上面类似问题的痛点,而线程池的优点有:

(1)复用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。

(2)能够有效的控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。

(3)能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。


线程池的构造方法

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                    BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)

上面代码是创建一个基本的线程池需要的参数,让我们通过图来简要的描述下:

img_2ef5edc908c60e932c019a2a55f5fa33.png
image.png

由上图可以简要的描述出创建一个基本的线程池需要的参数,以及各个参数的含义,下面将详细说明各个参数的具体含义。


CorePoolSize
线程的核心线程数。

默认情况下,核心线程数会在线程中一直存活,即使它们处于闲置状态。

如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么核心线程就会存在超时策略,这个时间间隔有keepAliveTime所决定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被停止。


maximumPoolSize
线程池所能容纳的最大线程数。

当活动线程数达到这个数值后,后续的新任务将会被阻塞。


keepAliveTime
非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收,当ThreadPoolExector的allowCoreThreadTimeOut属性设置为True时,keepAliveTime同样会作用于核心线程。


unit
用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分钟)等。

TimeUnit.NANOSECONDS  纳秒
TimeUnit.MICROSECONDS 微秒
TimeUnit.MILLISECONDS 毫秒
TimeUnit.SECONDS    秒
TimeUnit.MINUTES    分钟
TimeUnit.HOURS      小时
TimeUnit.DAYS       天

workQueue
线程池中的任务队列,通过线程池execute方法提交的Runnable对象会存储在这个参数中。

这个任务队列是BlockQueue类型,属于阻塞队列,就是当队列为空的时候,此时取出任务的操作会被阻塞,等待任务加入队列中不为空的时候,才能进行取出操作,而在满队列的时候,添加操作同样被阻塞。

如果有想了解的可以参考下这篇文章:
Java多线程-工具篇-BlockingQueue


threadFactory
线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,它只有一个方法,newThread(Runnable r),用来创建线程。

        ThreadFactory factory =new ThreadFactory() {
        //线程安全的Integer操作类
            private final AtomicInteger mCount =new AtomicInteger(1);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "new Thread #" + mCount.getAndIncrement());
            }
        };

线程池的源码解析

打开源码,先把线程池源码中除了构造参数,其他的一些基本属性,先给分析一下.

线程池的生命周期

    //这里在线程池统计数值,用AtomicInteger,它是一种线程安全的加减操作类
    //初始生命周期是RUNNING,工作线程的初始数量是0 
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    //进行移位操作需要的常量 Integer.SIZE =32 bit位
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //进行位运算需要的常量
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    //进行高位运算
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

将上述高位运算就是将 0 和1以及其他的数值在二进制下,向左移位29位,缺位用0补齐,实际结果就变成:

# 接受新任务,并且处理队列任务的状态
RUNNING     = 111 000...000 (29个0)
# 不接受新任务,但是会处理队列任务的状态  
SHUTDOWN    = 000 000...000 (29个0不包括前三位)
# 不接受新任务,并且也不会处理队列任务的状态
STOP        = 001 000...000 (29个0)
# 所有线程池内线程都将被终止,并且将workCount清零,在这里状态下将会运行terminated()方法(终止线程池的方法)
TIDYING     = 010 000...000 (29个0)
# terminated()方法以及结束的状态
TERMINATED  = 011 000...000 (29个0)
     /**
      * 获取到当前线程池的生命周期的状态
      */
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
     /**
      * 获取当前线程池的工作线程状态
      */
    private static int workerCountOf(int c)  { return c & CAPACITY; }
     /**
      * 通过或运算拼接线程的生命周期状态和工作线程的个数
      */
    private static int ctlOf(int rs, int wc) { return rs | wc; }

上面的三个函数是获取当前线程池状态的方法,这里简单介绍下:
(1) ctlOf()有两个参数,一个是生命周期状态,一个是当前线程池工作线程.
生命周期的状态格式:
XXX 0000…0000(29个0)
ctlOf()返回的值就是将工作线程数量转化成2进制拼接在生命周期的二进制后半段上.

(2) runStateOf()和workerCountOf()方法都是让生命周期的状态值与CAPACITY和CAPACITY的反码进行与运算,简明的说,就是获得二进制数的高位(前三位)和低位(后29位).

如果大家比较了解位运算可以发现:

CAPACITY        ------>     000 1111...1111 (29个1)
~CAPACITY      ------>     111 0000...0000 (29个0)

所以在进行与运算的同时,可以分别取出前3位和后29位,来分别代表线程池的生命周期和工作线程数.


其他属性

    /**
     * 无法执行任务的通知类
     * 在Android中不太常用
     */
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

当线程池无法执行任务,这可能由于任务队列已满或者是无法成功执行任务.这个时候ThreadPoolExecution就会调用handler的rejectedExecution方法来通知调用者.

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                " rejected from " +
                e.toString());
    }

默认情况下,rejectedExecution会抛出个RejectedExecutionException异常,来说明为什么当前无法执行任务.

ThreadPoolExecution为RejectedExecutionException提供了几个可选值:

----------------------------CallerRunsPolicy-------------------
//拒绝任务时,判断线程池的状态是否为SHUTDOWN,如果是任务将会被丢弃,如果不是的话任务会被继续执行.
public void rejectedExecution(Runnable r, ThreadPoolExecutor e){
            if (!e.isShutdown()) {
                r.run();
            }
        }

-------------------------AbortPolicy(默认值)---------------------
//拒绝任务时,直接抛出异常和原因
public void rejectedExecution(Runnable r, ThreadPoolExecutor e){
            throw new RejectedExecutionException(
            "Task " + r.toString() +
            " rejected from " +e.toString());
        }

-------------------------DiscardPolicy--------------------------
//就是单纯的拒绝任务而已,什么也不会发生,任务也将丢失public void rejectedExecution(Runnable r, ThreadPoolExecutor e){
        //什么没发生
        }

----------------------DiscardOldestPolicy-----------------------
//拒绝任务时,判断线程池的状态是否为SHUTDOWN,如果是任务将会被丢弃,如果不是的话,将当前请求队列中等待时间最长的任务弹出,将其加入队列中.
public void rejectedExecution(Runnable r, ThreadPoolExecutor e){
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }

比较重要的方法

线程池有两个执行的方法,分别是submit()和execute(),这两个方法本质的含义是一样的.

img_617eb8355a5a701d6933f79f5b2f2e05.png
image.png

从图上可以看出的,submit()其实还是需要调用execute()去执行任务,而submit()和execute()本质上的不同是submit()将包装好的任务进行了返回.

submit()

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        //还是通过调用execute
        execute(ftask);
        //最后会将包装好的Runable返回
        return ftask;
    }

//将Callable<T> 包装进FutureTask中
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

//可以看出FutureTask也是实现Runnable接口,因为RunableFuture本身就继承了Runnabel接口
public class FutureTask<V> implements RunnableFuture<V> {
    .......
}

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

execute()

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        //获得当前线程的生命周期对应的二进制状态码
        int c = ctl.get();
        //判断当前线程数量是否小于核心线程数量,如果小于就直接创建核心线程执行任务,创建成功直接跳出,失败则接着往下走.
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //判断线程池是否为RUNNING状态,并且将任务添加至队列中.
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //审核下线程池的状态,如果不是RUNNING状态,直接移除队列中
            if (! isRunning(recheck) && remove(command))
                reject(command);
                //如果当前线程数量为0,则单独创建线程,而不指定任务.
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //如果不满足上述条件,尝试创建一个非核心线程来执行任务,如果创建失败,调用reject()方法.
        else if (!addWorker(command, false))
            reject(command);
    }

下图是一张execute()方法的基本流程:


img_b9ec5b8e2ecd7f927ada02de98196e7a.png
image.png

从execute()方法中,能看出addWorker()方法,是创建线程(核心线程,非核心线程)的主要方法,而reject()就是线程创建失败的一个回调.


reject()

那我们来看一下reject()方法,这里就是通过上述的Handler将通知发出去.然后针对不同的类型的RejectedExecutionHandler,进行不同的处理,这里我们上文有介绍.

    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }


下面我们着重看下创建线程的方法:
addWorker()

参数 :

Runnable firstTask:

为传递进来需要执行的任务,也可以设置为null(在SHUTDOWN情况下,单纯的创建线程来执行任务).

boolean core:

需要创建的线程是否需要是核心线程.

 private boolean addWorker(Runnable firstTask, boolean core) {
        //类似goto,是Java的标识符,在这里出现是为了防止在多线程的情况下,compareAndIncrementWorkerCount(),计算线程池状态出现问题,而设立重试的关键字.
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            //看似判断条件很麻烦
            //分拆后主要两点
            //线程已经处于STOP或者即将STOP的状态
            //或者 处于SHUTDOWN状态,并且传递的任务为null,此时队列不为空还需要增加线程,除了这种情况,其他情况都不需要增加线程
            //以上的情况就不需要
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            //判断当前工作线程数量是否超过最大值
            //或者当前工作线程数量超过 核心线程数或者最大线程数,这个值根据第二个布尔变量决定
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;

            //这段函数是判断 线程池状态的统计更新成没成功
            //如果成功直接跳出这个循环,继续执行
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                //如果不成功则跳到外层循环入口,重新执行.
                retry inner loop
            }
        }

        //下面是创建线程的过程,并且在创建线程的过程中加锁
        //Worker就是线程的一个包装类.
        //这里分别对线程的创建成功和失败分别做出了处理.
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
            //创建线程的过程中,加锁防止并发现象发生.
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    int rs = runStateOf(ctl.get());

                    //从这里可以看出线程池创建线程,只会在两种情况下创建:
                    //1.线程池在RUNNING状态(rs<SHUTDOWN)
                    //2.线程池处于SHUTDOWN状态,并且任务为null,但是此时任务队列不为空,需要继续增加线程来加快处理进度.
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        //在这里就是先检查下Thread状态,防止意外发生.
                        if (t.isAlive()) 
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        //这里做了一个容量的判断 
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //如果线程已经增加成功,然后设置标志
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            //最后如果线程没有开始,就分发到添加线程失败,通过标志位来判断线程是否被添加成功.
            if (! workerStarted)
                addWorkerFailed(w);
        }
        //如果添加成功就返回true,否则添加失败就返回false.
        return workerStarted;
    }

addWorker()方法的注意事项:

(1)增加一个线程,并且会为其绑定core或者maximum的线程标志.

(2)如果成功添加线程来执行当前任务,那么当前线程池的状态会被刷新.

(3)在添加第一个任务firstTask的这种情况下,新的工作线程会被创建后立即执行任务.

(4)该方法会在线程池STOP状态或者符合资格去关闭会返回false.

(5)线程工厂创建线程失败的时候,同样也会返回false.

(6)在由于线程创建失败,线程工厂返回的线程为null,或者发生异常(通常由于在线程执行的过程中发生了OOM),线程池会进行回滚操作.
img_05aac506f2ff5e4e92e9ec1b1bc00ec0.png
image.png

addWorker()方法执行的几个阶段

第一阶段 :

状态检查

在创建线程时,首先检查线程池状态,防止线程处于STOP,TIDYING,TERMINATED状态,如果处于上述状态直接返回false.

然后对于在SHUTDOWN状态下,只有当前任务队列不为空,并且传递的任务参数为null.这种状态下可以创建线程来执行剩余任务,除此之外全部直接返回false.
   if (rs >= SHUTDOWN &&! (rs == SHUTDOWN && firstTask == null 
            &&! workQueue.isEmpty()))
                return false;

第二阶段 :

判断当前线程池的能否创建线程以及可以创建之后的数量添加校验

(1)当前线程的数量是否超过线程池的最大容量,以及根据core参数来判断是否超过设置的核心线程数,和最大线程数.

(2)通过第一步之后就可以创建线程,这里需要用到compareAndIncrementWorkerCount()通过原子操作来更新线程池的线程数量变化,如果变化数量失败,这里有一个重试机制,这个retry关键字就是来完成这个操作.

(3)这里注明下CAPACITY这个常量就是线程池的线程数量的极限
CAPACITY  = 1>>29 -1 =2^29-1
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
            }

第三阶段 :

创建线程

通过上述阶段,那么就可以创建线程了,这里设置了两个初始的标志位,来判断被创建线程的状态.
        boolean workerStarted = false;
        boolean workerAdded = false;
如果最终线程创建并添加成功,则返回true,如果线程最终没有被运行,则调用addWorkerFailed()方法.

由于逻辑并不复杂,这里就不贴代码了.


其他相关方法

addWorkedFailed()

在addWorker()方法中,如果线程创建之后,没有最终运行(workerStarted=false)这时候会调用addWorkedFailed()方法.

    /**
     * 回滚工作线程的创建操作:
     * 1.如果线程的包装类Worker存在,就将其remove掉.
     * 2.remove掉添加线程失败的Worker,需要刷新当前工作线程的数量
     * 3.尝试终止操作,并且终止这个线程的操作.
     */
    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            //尝试停止操作.
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    }

tryTerminate()

而在addWorkedFailed()方法中,我们发现除了回滚操作,它还调用了tryTerminate()方法,尝试着去停止线程池.因为线程池创建线程失败一般由于异常引起(或OOM),所以这时候需要让线程池进行停止操作.

注意事项:

如果发生以下两种情况,使用该方法将会将线程池转换为终止状态(TERMINATED):

1.SHUTDOWN状态下,队列为空的情况下.

2.STOP状态下.

如果符合上述条件,可以转换终止状态时,这时会中断当前线程池内空闲的线程,以确保终止的信号的传递.

    final void tryTerminate() {
        for (;;) {
            int c = ctl.get();
            //检测当前是RUNNING状态,或者已经停止(TERMINATED)的状态,或者SHUTDOWN状态下,队列不为空.
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;

                //如果工作线程的数量不为空,这时候需要处理空闲线程,这里只中断一个其中一个线程,这里博主认为是将线程池的状态由SHUTDOWN向STOP状态过渡的信号.
            if (workerCountOf(c) != 0) { 
                interruptIdleWorkers(ONLY_ONE);
                return;
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
            //设置当前的线程池状态为TIDYING,如果设置失败,还会进入循环直到设置成功.
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                    try {
                    //停止方法的空实现
                        terminated();
                    } finally {
                        //最终线程池会设置为停止状态
                        ctl.set(ctlOf(TERMINATED, 0));
                 //设置可重新入锁的标志,将被锁隔离的在外等待的所有线程唤醒.  
                      termination.signalAll();
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }

        }
    }

interruptIdleWorkers()

而在tryTerminate()方法中,这里中断线程的操作就是由interruptIdleWorkers()方法进行的.

这个方法作用很明确,就是设置线程中断操作的方法,唯一注意的地方就是参数onlyOne:

如果为true,只中断工作线程中的一个线程.
如果为false,中断所有的工作线程.
 private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                //检查线程的状态
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                //如果onlyOne参数为True,则只执行一次就跳出.
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

shutdown()
而中断所有空闲的线程方法则是shutdown()方法,它的核心方法还是调用interruptIdleWorkers()方法.

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //校验线程的状态
            checkShutdownAccess();
            //设置线程池状态为SHUTDOWN
            advanceRunState(SHUTDOWN);
            //中断所有空闲进程.调用的interruptIdleWorkers(false);
            interruptIdleWorkers();
            //需要自己实现,在中断所有线程可定制的操作
            onShutdown(); 
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

注意事项:
(1)在shutdown()执行时可以让现有的任务被执行,但是新的任务不在会被处理.

(2)如果已经是SHUTDOWN状态,那么继续调用不会产生任何效果.

(3)shutdown()方法只会中断空闲的线程,但是不会影响到已经存入队列的任务,如果需要停止线程池的运行,可以使用awaitTermination()方法.


awaitTermination()

阻塞方法,强行等待当前队列中的任务全部为TERMINATED状态,可以设置超时时间.

参数:d
timeout —- 设置超时时间
unit —- 设置超时时间的单位

 public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        //设置时间
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        //这是死循环,当线程池的状态为TERMINATED时,跳出循环返回true,也就是所有任务都完成.否则超时或者线程中断则返回false.
            while (!runStateAtLeast(ctl.get(), TERMINATED)) {
                if (nanos <= 0L)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
            return true;
        } finally {
            mainLock.unlock();
        }
    }

线程池的分类

Android中最常见的四类具有不同功能特性的线程池:

1.FixedThreadPool

//特点:
//核心线程数和最大线程数相同.
//无超时时间
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(
                nThreads, nThreads,
                0L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>()
        );

这是一种数量固定的线程池,当线程处于空闲的时候,并不会被回收,除非线程池被关闭.

当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来.

由于FixedThreadPool中只有核心线程并且这些核心线程不会被回收,这意味着它能够更加快速地响应外界的请求.

通过构造方法可以看出,FixedThreadPool只有核心线程,并且超时时间为0(即无超时时间),所以不会被回收.

img_57ee878f1bfc78465d13eabfd50beb40.png
image.png

2.CacheThreadPool

//无核心线程,并且最大线程数为int的最大值.
//超时时间为60s
//队列为SynchronousQueue同步阻塞队列,队列中没有任何容量.只有在有需求的情况下,队列中才可以试着添加任务.

    public static  ExecutorService newCacheThreadPool(){
        return  new ThreadPoolExecutor(
                0,Integer.MAX_VALUE,
                60L,TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>()
        );
    }

它是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数为Integer.MAX_VALUE(也就相当于线程池的线程数量可以无限大).

当线程池中所有线程都处于活动的状态时,线程池会创建新的线程来处理新任务,否则就会复用空闲线程来处理.

值得注意的是,这个线程池中储存任务的队列是SynchronousQueue队列,这个队列可以理解为无法储存的队列,只有在可以取出的情况下,才会向其内添加任务.

从整个CacheThreadPool的特性来看:

(1)比较适合执行大量的耗时较少的任务.

(2)当整个线程都处于闲置状态时,线程池中的线程都会超时而被停止,这时候的CacheThreadPool几乎不占任何系统资源的.

img_626244e5c3f3a4171108d41b1bb6c959.png
image.png

3.ScheduledThreadPool

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSzie) {
        return new ScheduledThreadPoolExecutor(corePoolSzie);
    }

//核心线程数是固定的,非核心线程无限大,并且非核心线程数有10s的空闲存活时间

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
                DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
                new DelayedWorkQueue());
    }

它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收.

ScheduThreadPool这类线程池主要用于执行定时任务和具有固定周期的重复任务.

而DelayedWorkQueue这个队列就是包装过的DelayedQueue,这个类的特点是在存入时会有一个Delay对象一起存入,代表需要过多少时间才能取出,相当于一个延时队列.

img_fd86504cbf8a4978aa22000bac9053f8.png
image.png

4.SingleThreadExecutor

    public static ExecutorService newSingleThreadExecutor() {
        return Executors.newSingleThreadExecutor();
    }
    //特点:
    //线程中只有一个核心线程
    //并且无超时时间
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                        0L, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>()));
    }

这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行.

SingleThreadExecutor的意义在于统一外界所有任务到一个线程,这使得这些任务之间不需要处理线程同步的问题.

img_c2675a94aceaf7351d285ab4b34838eb.png
image.png

参考文档:

1.安卓开发艺术探索

2.ThreadPoolExecutor解析-主要源码研究
http://blog.csdn.net/wenhuayuzhihui/article/details/51377174

3.理解java线程的中断(interrupt)
http://blog.csdn.net/canot/article/details/51087772

</article>

目录
相关文章
|
4月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
【7月更文挑战第28天】在Android开发中,确保UI流畅性至关重要。多线程与异步编程技术可将耗时操作移至后台,避免阻塞主线程。我们通常采用`Thread`类、`Handler`与`Looper`、`AsyncTask`及`ExecutorService`等进行多线程编程。
57 2
|
4月前
|
Java Android开发
Android面试题经典之Glide取消加载以及线程池优化
Glide通过生命周期管理在`onStop`时暂停请求,`onDestroy`时取消请求,减少资源浪费。在`EngineJob`和`DecodeJob`中使用`cancel`方法标记任务并中断数据获取。当网络请求被取消时,`HttpUrlFetcher`的`cancel`方法设置标志,之后的数据获取会返回`null`,中断加载流程。Glide还使用定制的线程池,如AnimationExecutor、diskCacheExecutor、sourceExecutor和newUnlimitedSourceExecutor,其中某些禁止网络访问,并根据CPU核心数动态调整线程数。
129 2
|
20天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
36 4
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
104 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
2月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
78 5
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
67 10
|
2月前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
2月前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
3月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
57 4
|
3月前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享安卓与iOS开发中的线程管理比较
【8月更文挑战第30天】本文将探讨网络安全与信息安全的重要性,并分享关于网络安全漏洞、加密技术和安全意识的知识。我们将了解常见的网络攻击类型和防御策略,以及如何通过加密技术和提高安全意识来保护个人和组织的信息安全。