文章目录
  1. 1. 主线程和子线程
  2. 2. Android中的线程形态
    1. 2.1. AsyncTask
      1. 2.1.1. 注意事项:
    2. 2.2. HandlerThread
    3. 2.3. IntentService
  3. 3. Android中的线程池
    1. 3.0.1. 线程池的优点:
    2. 3.0.2. 线程运行机制
    3. 3.0.3. 线程池的作用
    4. 3.0.4. 线程池涉及的类
  4. 3.1. Executors介绍(把握使用,把握常见线程池)
    1. 3.1.1. ThreadPoolExecutor介绍
    2. 3.1.2. 构造相关参数解释
    3. 3.1.3. 基础API的介绍
    4. 3.1.4. 任务提交给线程池之后的处理策略
  5. 3.2. 阻塞队列的介绍(BlockingQueue)

在操作系统中,线程是操作系统调度的最小单元,当系统中存在大量线程时,系统会通过时间片轮转的方式调度每个线程,当进程中频繁的创建和销毁线程的时候应该采用线程池,线程池会缓存一定数量的线程,避免频繁创建和销毁线程带来的系统开销。

主线程和子线程

(1) 在Java中默认情况下一个进程只有一个线程,也就是主线程,其他线程都是子线程,也叫工作线程。Android中的主线程主要处理和界面相关的事情,而子线程则往往用于执行耗时操作。线程的创建和销毁的开销较大,所以如果一个进程要频繁地创建和销毁线程的话,都会采用线程池的方式。

(2) 在Android中除了Thread,还有HandlerThread、AsyncTask以及IntentService等也都扮演着线程的角色,只是它们具有不同的特性和使用场景。AsyncTask封装了线程池和Handler,它主要是为了方便开发者在子线程中更新UI。HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler。IntentService是一个服务,它内部采用HandlerThread来执行任务,当任务执行完毕后就会自动退出。因为它是服务的缘故,所以和后台线程相比,它比较不容易被系统杀死。

(3). 从Android 3.0开始,系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出NetworkOnMainThreadException这个异常,这样做是为了避免主线程由于被耗时操作所阻塞从而出现ANR现象。

Android中的线程形态

AsyncTask

AsyncTask是一个抽象泛型类,它提供了Params、Progress、Result三个泛型参数,如果task确实不需要传递具体的参数,那么都可以设置为Void。下面是它的四个核心方法,其中doInBackground不是在主线程执行的。
onPreExecute、doInBackground、onProgressUpdate、onPostResult

注意事项:

  • AsyncTask必须在主线程中加载
  • 必须在主线程中创建
  • execute必须在UI线程调用
  • 一个AsyncTask对象只能执行一次,即只能调用一次execute方法
  • 在Android1.6的时候线程池是并行任务,在Android3.0为了避免并发错误采用串行执行任务。但是我们仍然可以用过executeOnExecutor方法并行执行任务。

HandlerThread

它继承了Thread,是一种可以使用Handler的Thread.他的实现也很简单,就是在run方法中通过Looper。prepare来创建消息队列,并通过Looper.loop来开启消息循环,这样在使用的时候就可以在HandlerThread中创建Handler了。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式开通知它执行一个具体的任务,它的一个具体实现是IntentService。当我们不需要使用Handler的时候可以通过quit获取quitSafelt方法终结线程的执行。

IntentService

继承了Service是一个抽象类,必须创建它的子类才可以使用它,IntentService可以执行后台耗时任务,当任务执行完毕之后会自动停止,因为他是服务,所以他的优先级比普通的线程高,比较适合执行一些高优先级的后台任务。

Android中的线程池

Android里面,耗时的网络操作,都会开子线程,在程序里面直接开过多的线程会消耗过多的资源,在众多的开源框架中也总能看到线程池的踪影,所以线程池是必须要会把握的一个知识点;

线程池的优点:

  • 重用线程池中的线程,避免频繁创建和销毁带来的性能开销
  • 可以有效控制线程池中的最大并发数,避免大量线程之间互相抢占系统资源导致阻塞
  • 可以对线程进行简单管理。

线程运行机制

  • 开启线程过多,会消耗cpu资源
  • 单核cpu,同一时刻只能处理一个线程,多核cpu同一时刻可以处理多个线程
  • 操作系统为每个运行线程安排一定的CPU时间—-时间片,系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因为时间相当短,多个线程频繁地发生切换,因此给用户的感觉就是好像多个线程同时运行一样,但是如果计算机有多个CPU,线程就能真正意义上的同时运行了.

线程池的作用

  • 线程池是预先创建线程的一种技术。线程池在还没有任务到来之前,创建一定数量的线程,放入空闲队列中,然后对这些资源进行复用。减少频繁的创建和销毁对象。
  • 频繁创建和销毁线程耗资源,耗时间
  • 因为有的线程执行时间比创建和销毁一个线程的时间还短

线程池涉及的类

  • Executor:Java里面线程池的顶级接口。
  • ExecutorService:真正的线程池接口。
  • ScheduledExecutorService:能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
  • ThreadPoolExecutor(重点):ExecutorService的默认实现。
  • ScheduledThreadPoolExecutor:继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
  • Executors:可以一行代码创建一些常见的线程池。

Executors介绍(把握使用,把握常见线程池)

Executors:jdk1.5之后的一个新类,提供了一些静态方法,帮助我们方便的生成一些常用的线程池,ThreadPoolExecutor是Executors类的底层实现

1.newSingleThreadExecutor

创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行>所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池>保证所有任务的执行顺序按照任务的提交顺序执行。

2.newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3.newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4.newScheduledThreadPool

创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

ThreadPoolExecutor介绍

1
2
3
4
5
6
7
8
//构造方法
public ThreadPoolExecutor(int corePoolSize,//核心池的大小
int maximumPoolSize,//线程池最大线程数
long keepAliveTime,//保持时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler) //异常的捕捉器

构造相关参数解释

  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

  • maximumPoolSize:’线程池最大线程数
    这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

  • unit:参数keepAliveTime的时间单位,有7种取值

    1
    2
    3
    4
    5
    6
    7
    TimeUnit.DAYS; //天
    TimeUnit.HOURS; //小时
    TimeUnit.MINUTES; //分钟
    TimeUnit.SECONDS; //秒
    TimeUnit.MILLISECONDS; //毫秒
    TimeUnit.MICROSECONDS; //微妙
    TimeUnit.NANOSECONDS; //纳秒
  • workQueue : 任务队列,是一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,参考BlockingQueue

    1
    2
    3
    ArrayBlockingQueue
    LinkedBlockingQueue
    SynchronousQueue
  • threadFactory : 线程工厂,如何去创建线程的

  • handler : 任务队列添加异常的捕捉器,参考 RejectedExecutionHandler

    1
    2
    3
    4
    ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

基础API的介绍

  • isShutdown() : 判断线程池是否关闭
  • isTerminated() : 判断线程池中任务是否执行完成
  • shutdown() : 调用后不再接收新任务,如果里面有任务,就执行完
  • shutdownNow() : 调用后不再接受新任务,如果有等待任务,移出队列;有正在执行的,尝试停止之
  • submit() : 提交执行任务
  • execute() : 执行任务

任务提交给线程池之后的处理策略

  1. 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建执行这个任务;
  2. 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中
    1. 若添加成功,则该任务会等待空闲线程将其取出去执行;
    2. 若添加失败(一般来说是任务缓存队列已满,针对的是有界队列),则会尝试创建新的线程去执行这个任务;
  3. 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
  4. 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

阻塞队列的介绍(BlockingQueue)

阻塞队列,如果BlockingQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒,同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间时才会被唤醒继续操作。

  1. 基础API介绍
    • 往队列中加元素的方法
      • add(E) : 非阻塞方法, 把元素加到BlockingQueue里,如果BlockingQueue可以容纳,则返回true,否则抛出异常。
      • offer(E) : 非阻塞, 表示如果可能的话,将元素加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false。
      • put(E):阻塞方法, 把元素加到BlockingQueue里,如果BlockingQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里有空间再继续。
    • 从队列中取元素的方法
      • poll(time): 阻塞方法,取走BlockingQueue里排在首位的元素,若不能立即取出,则可以等time参数规定的时间,取不到时返回null。
      • take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的对象被加入为止。
  2. 子类介绍
    • ArrayBlockingQueue(有界队列): FIFO 队列,规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小
    • LinkedBlockingQueue(无界队列):FIFO 队列,大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定。
    • PriorityBlockingQueue:优先级队列, 类似于LinkedBlockingQueue,但队列中元素非 FIFO, 依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序
    • SynchronousQueue(直接提交策略): 交替队列,队列中操作时必须是先放进去,接着取出来,交替着去处理元素的添加和移除,这是一个很有意思的阻塞队列,其中每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。因此此队列内部其 实没有任何一个元素,或者说容量是0,严格说并不是一种容器。由于队列没有容量,因此不能调用peek操作,因为只有移除元素时才有元素。

未经许可不得转载,转载请注明zilianliuxue的blog,本人保留所有版权。

文章目录
  1. 1. 主线程和子线程
  2. 2. Android中的线程形态
    1. 2.1. AsyncTask
      1. 2.1.1. 注意事项:
    2. 2.2. HandlerThread
    3. 2.3. IntentService
  3. 3. Android中的线程池
    1. 3.0.1. 线程池的优点:
    2. 3.0.2. 线程运行机制
    3. 3.0.3. 线程池的作用
    4. 3.0.4. 线程池涉及的类
  4. 3.1. Executors介绍(把握使用,把握常见线程池)
    1. 3.1.1. ThreadPoolExecutor介绍
    2. 3.1.2. 构造相关参数解释
    3. 3.1.3. 基础API的介绍
    4. 3.1.4. 任务提交给线程池之后的处理策略
  5. 3.2. 阻塞队列的介绍(BlockingQueue)