安卓中的线程和线程池

本文出自 “阿敏其人” 简书博客,转载或引用请注明出处。

Android沿用了Java的线程模式,其中线程也分主线程和子线程。

一、线程

1、主线程和子线程

一个程序的运行,就是一个进程的在执行,一个进程里面可以拥有很多个线程。

  • 主线程:也叫UI线程,用于运行四大组件和处理他们用户的交互。(主线程就是指进程所拥有的线程,在Java中默认一个进程只有一个线程,那么默认的线程就是我们的主线程)

  • 子线程: 用于执行耗时操作,比如 I/O操作和网络请求等。(安卓3.0以后要求耗访问网络必须在子线程种执行)更新UI的工作必须交给主线程,子线程在安卓里是不允许更新UI的。

2、安卓里,谁是线程

在安卓的世界里面,扮演线程的觉有有如下四个:

  • 1,Thread:线程就是它。它本来就是线程,最根本的就是它
  • 2,AsyncTask:封装了线程池和Handler
  • 3、HandlerThread:是同一个具有消息循环的线程,在他的内部可以使用Handler
  • 4,IntentServer : 内部使用HandlerThread来执行任务,任务完成后自动退出,因为是服务,所以优点是优先级高,系统尽量保证它存活。

二、线程池

1、线程的产生和销毁都会消耗性能

在操作系统中,线程是可以调度的最小单元,线程不能无限制产生,因为创建线程和销毁线程都会有相应的性能开销。

当系统中存在大量的线程的时候,系统会通过时间片轮转的方式调度每个线程,因此在大量线程的情况下没办法让每一个线程都得到绝对的并行,除非线程数量小于或者等于CPU的核心数,一般来说这是不可能的。
所以不可以在一个进程里面大量创建和销毁线程,这是非常消耗性能的行为,所以我们需要使用管理线程,这个管理的工具就叫做—— 线程池

    线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
    比如:一个应用要和网络打交道,有很多步骤需要访问网络,为了不阻塞主线程,每个步骤都创建个线程,在线程中和网络交互,用线程池就变的简单,线程池是对线程的一种封装,让线程用起来更加简便,只需要创一个线程池,把这些步骤像任务一样放进线程池,在程序销毁时只要调用线程池的销毁函数即可。

2、使用线程池的好处

  • 1,重用线程,避免重复地创建和销毁进程带来的没必要的性能开销
  • 2,管理控制最大并发数,避免大量的进程不受控制得因抢占系统资源而导致阻塞,也就是界面卡顿。
  • 3,提供定时执行和指定间隔循环等功能

4、Executor 和 ThreadPoolExecutor

安卓中的线程池的概念来源于 Java 中的 Executor。
Executor是一个接口,真正的线程池的实现是 ThreadPoolExecutor 。
ThreadPoolExecutor提供了一系列的参数来配置线程池,通常来说,安卓的线程主要有4种,后面会陆续说到,但是因为Android中的线程池都是直接或者间接通过配置ThreadPoolExecutor来实现的,所以我们这里先了解ThreadPoolExecutor。

5、ThreadPoolExecutor 的构造方法的参数

ThreadPoolExecutor是java.util.concurrent下的一个类,ThreadPoolExecutor有好几个构造方法,我们主要明白了他的构造的方法含义,就大概明白ThreadPoolExecutor是怎么用的了。

  • 常用的构造的方法
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

  • 最长的构造方法
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

因为最长,所以我们分析这个最长的

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

如果将executor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略(超时判断),如果在keepAliveTime指定的时间之外还处于等待状态的话,核心线程也会被终止(所以核心线程也不是可以毫无条件地牛逼下去的)

  • maximumPoolSize 线程池所能容纳的最大线程数,当活动线程达到这个数值之后,后续的新任务将会被阻塞在外面。
  • keepAliveTime 非核心线程闲置的超时时长,如果超过了这个时长,非核心线程就会被回收。
    当 ThreadPoolExecutor 的allowCoreThreadTimeOut属性被设置为true时,keepAliveTime也会作用于核心线程
  • unit 用于指定 keepAliveTime的参数的时间单位,这是一个枚举,常用的有 TimeUnit.MILLISECONDS (毫秒),TimeUnit.SECONDS (秒),TimeUnit.MINUTES (分钟)等
  • workQueue 线程池中的任务队列,通过线程池的 execute 方法提交到 Runnable 对象会存储在这个参数中。
  • threadFactort 线程工厂,为线程池提供创建新线程的功能,ThreadFactory是一个接口,它只有一个方法,Thread newThread(Runnable r)
  • RejectedExecutionHandler(不常用) 当线程池无法执行新任务时,(任务队列已满或者其他原因),那么 rejectedExecutio会抛出一个异常给调用者。

6、线程池的运行机制

一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是 Runnable类型对象的run()方法。

当一个任务通过execute(Runnable)方法欲添加到线程池时:

  • 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量等于 corePoolSize,但是任务队列 workQueue未满,那么任务被放入任务队列。
  • 如果此时线程池中的数量大于corePoolSize,任务队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
  • 如果此时线程池中的数量大于corePoolSize,任务队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

也就是:处理任务的优先级为:
核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

《安卓中的线程和线程池》 新任务添加到线程池流程简图

三、线程池的分类

Android中常见的有4中线程池,他们都是直接直接或者间接配置ThreadPoolExecutor来实现自己的功能特性的,他们分别是

  • FixedThreadPool
  • CacheThreadPool
  • SchedeledThreadPool
  • SingleThreadExecutor

这几种线程池的创建都是调用 java.util.concurrent.Executors 里面的方法的。

1、FixedThreadPool

  • 创建:
    • FixedThreadPool通过 newFixedThreadPool 方法来创建

corePoolsize等于maximumPoolSize

  • 特点:
    • 线程池里面线程数固定(只有核心线程,除非线程池被关闭要不然核心线程不会被回收)
    • 没有超时机制
    • 当线程池里面所有线程都处于活动状态时,新任务会处于等待状态,直到有新的线程能空闲出来。
    • 任务队列的大小没有限制

newFixedThreadPool 方法在 java.util.concurrent.Executors 里面

84     public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
85         return new ThreadPoolExecutor(nThreads, nThreads,
86                                       0L, TimeUnit.MILLISECONDS,
87                                       new LinkedBlockingQueue<Runnable>(),
88                                       threadFactory);
89     }

查看源码

2、CacheedThreadPool

  • 创建:

    • CacheedThreadPool 通过 newCachedThreadPool 方法来创建
  • 特点:

    • 线程池的核心线程数量不定,只有非核心线程
    • 最大线程数为 Integer.MAX_VALUE (相当于线程数可以任意多)
    • 当线程池中所有线程任务都处于活跃状态的时候,如果有新的任务请求执行,那么会创建的新的线程来处理新的任务。(CacheedThreadPool的任务队列相当于一个空集合,意味着任何任务都会被立即执行)
    • 线程池的任务都会有超时机制,时长为60秒,超过60秒的闲置线程就会被回收
  • CacheedThreadPool 常用场景:
    适合执行大量耗时耗时较少的任务,当整个线程池都处于闲置状态的时候,线程池中的任务会因为超时而被停止,所以他几乎是不占任何系统资源的。

    newCachedThreadPool 方法如下

59     public static ExecutorService newFixedThreadPool(int nThreads) {
60         return new ThreadPoolExecutor(nThreads, nThreads,
61                                       0L, TimeUnit.MILLISECONDS,
62                                       new LinkedBlockingQueue<Runnable>());
63     }

3、ScheduledThreadPool

  • 创建

    • 通过 newScheduledThreadPool 方法创建
  • 特点

    • 核心线程数固定,但是非核心线程数是没有限制的
    • 非核心线程闲置时会被激励回收
  • 使用场景:
    执行 定时任务具有固定周期重复任务

    newScheduledThreadPool 方法

Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically.
Parameters:
corePoolSize the number of threads to keep in the pool, even if they are idle.
Returns:
a newly created scheduled thread pool
Throws:
java.lang.IllegalArgumentException if corePoolSize < 0
217
218    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
219        return new ScheduledThreadPoolExecutor(corePoolSize);
220    }

4、SingleThreadExecutor

  • 创建
    • 通过 newSingleThreadExecutor 方法创建

*特点:
* 线程池内部只有一个核心线程,确保所有任务都在同一个线程中按顺序执行。
* 统一所有的外界任务到同一个线程,任务之间不需要处理线程同步的问题

newSingleThreadExecutor 方法:

Creates an Executor that uses a single worker thread operating off an unbounded queue. (Note however that if this single thread terminates due to a failure during execution prior to shutdown, a new one will take its place if needed to execute subsequent tasks.) Tasks are guaranteed to execute sequentially, and no more than one task will be active at any given time. Unlike the otherwise equivalent newFixedThreadPool(1) the returned executor is guaranteed not to be reconfigurable to use additional threads.
Returns:
the newly created single-threaded Executor
103
104    public static ExecutorService newSingleThreadExecutor() {
105        return new FinalizableDelegatedExecutorService
106            (new ThreadPoolExecutor(1, 1,
107                                    0L, TimeUnit.MILLISECONDS,
108                                    new LinkedBlockingQueue<Runnable>()));
109    }

4种线程池使用简单演示

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
        fixedThreadPool.execute(command);
        
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        cachedThreadPool.execute(command);
        
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
        // 2000ms后执行command
        scheduledThreadPool.schedule(command, 2000, TimeUnit.MILLISECONDS);
        // 延迟10ms后,每隔1000ms执行一次command
        scheduledThreadPool.scheduleAtFixedRate(command, 10, 1000, TimeUnit.MILLISECONDS);

        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(command);

参考:
《Android开发艺术探索》
线程池ThreadPoolExecutor使用简介

    原文作者:阿敏其人
    原文地址: https://www.jianshu.com/p/78444487c5ab
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞