Android线程之线程池

ThreadPoolExecutor(线程池)

构造方法

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

具体参数及其含义

  • int corePoolSize
    核心线程数量,核心线程存在两种状态,与参数设置有关。
      存活状态:allowCoreThreadTimeOut 默认设置为false,此设置下,核心线程一直处于存活状态,即使核心线程无任务操作,处于空闲状态。
      终止状态: allowCoreThreadTimeOut 设置为true时,超过keepAliveTime设定时常后,核心线程终止,被回收。

  • int maximumPoolSize
    线程池所允许存在的最大线程数量,超过最大线程数后,后续的新任务会被阻塞。

  • long keepAliveTime
    当线程处于空闲状态后,允许存活的时长,超过此时长后,线程会被终止回收。根据allowCoreThreadTimeOut参数不同,限定的线程不同。
      限定非核心线程:当allowCoreThreadTimeOut参数值为默认false时,只对非核心线程有限制。
      限定核心线程与非核心线程:当allowCoreThreadTimeOut参数值为true时,对线程池中的核心与非核心都由限制,超过限定时长后,线程都会终止并回收。

  • TimeUnit unit
    keepAliveTime的事件单位,TimeUnit类提供枚举,有纳秒、微秒、毫秒等,具体如下。

TimeUnit.NANOSECONDS:纳秒
TimeUnit.MICROSECONDS:微秒
TimeUnit.MILLISECONDS:毫秒
TimeUnit.SECONDS:秒
TimeUnit.MINUTES:分
TimeUnit.HOURS:小时
TimeUnit.DAYS:天
  • BlockingQueue<Runnable> workQueue
    线程池任务队列,阻塞队列,主要用来存储已经提交但尚未分配给线程执行的任务(线程池execute方法提交的Runnable对象)。

  • ThreadFactory threadFactory
    线程工厂,interface类型,只有一个方法Thread newThread(Runnable r); 用来为线程池创建新线程。

  • RejectedExecutionHandler handler
    interface类型,只有一个方法void rejectedExecution(Runnable r, ThreadPoolExecutor executor),线程池无法执行新任务时,ThreadPoolExecutor调用handler的该方法通知调用者。

调用策略

  1. 如果线程池中线程的数量小于核心线程数量,新任务来之后,开启新的核心线程去执行任务。
  2. 如果线程池中核心线程数量已经饱和,新任务来之后,则放到workQueue任务队列,代核心线程执行完任务后,从任务队列中取出待处理任务,继续处理。
  3. 如果线程池中核心线程数量已经饱和,workQueue任务队列中任务数也达到最大值,再来新任务,则开启一个非核心线程,进行处理任务。之后非核心线程和核心线程一同处理workQueue任务队列中任务。
  4. 如果线程池中核心线程数量饱和,workQueue任务队列中任务数也达到最大值,非核心线程也达到最大值,当再来到新任务后,那么线程池就调用RejectedExecutionHandler的rejectedExecution方法拒绝该任务。
  5. 特殊情况,如果线程池中无核心线程,并且任务数不大于任务队列限制,则线程池只开启一个非核心线程进行处理。

调用策略示例验证

  为了更好的理解调用策略,采用具体的示例,更清晰明了的进行理解。

package com.bamboolmc.threadpro;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPool {
    private static int taskSize = 2;
    private static int corePoolSize = 3;
    private static int maximumPoolSize = 4;
    private static int queueSize = 3;

    public static class TestTask implements Runnable{
        @Override
        public void run() {
            if (taskSize>0){
                try {
                    //模拟任务处理
                    Thread.sleep(500);
                    System.out.println(System.currentTimeMillis()+Thread.currentThread().getName()
                            +" 完成一个任务,编号为t" + (taskSize--));

                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }


    public static void main(String args[]) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                1,
                TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(queueSize));
        TestTask task;
        int size = taskSize;
        for (int i = 0; i < size; i++) {
            task = new TestTask();
            executor.execute(task);
        }
        executor.shutdown();

    }
}
对比调用策略1
    private static int taskSize = 2;
    private static int corePoolSize = 3;
    private static int maximumPoolSize = 4;
    private static int queueSize = 3;

任务数量2,核心线程数量3
运行结果:

1527001137241pool-1-thread-2 完成一个任务,编号为t1
1527001137241pool-1-thread-1 完成一个任务,编号为t2
对比调用策略2

修改如下

    private static int taskSize = 5;
    private static int corePoolSize = 3;
    private static int maximumPoolSize = 4;
    private static int queueSize = 3;

任务数量5,核心线程数量3,任务队列3
运行结果:

1527001567366pool-1-thread-2 完成一个任务,编号为t4
1527001567366pool-1-thread-1 完成一个任务,编号为t5
1527001567366pool-1-thread-3 完成一个任务,编号为t3
1527001567869pool-1-thread-1 完成一个任务,编号为t1
1527001567869pool-1-thread-2 完成一个任务,编号为t2
对比调用策略3

修改如下

    private static int taskSize = 7;
    private static int corePoolSize = 3;
    private static int maximumPoolSize = 4;
    private static int queueSize = 3;

任务数量7, 核心线程数量3,任务队列3,非核心线程1
运行结果:

1527002003014pool-1-thread-3 完成一个任务,编号为t6
1527002003014pool-1-thread-2 完成一个任务,编号为t4
1527002003014pool-1-thread-1 完成一个任务,编号为t7
1527002003014pool-1-thread-4 完成一个任务,编号为t5
1527002003516pool-1-thread-3 完成一个任务,编号为t3
1527002003516pool-1-thread-1 完成一个任务,编号为t2
1527002003516pool-1-thread-2 完成一个任务,编号为t1
对比调用策略4

修改如下

    private static int taskSize = 8;
    private static int corePoolSize = 3;
    private static int maximumPoolSize = 4;
    private static int queueSize = 3;

任务数量8, 核心线程数量3,任务队列3,非核心线程1
运行结果:
第8个任务运行时,线程池拒绝,抛出异常

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.bamboolmc.threadpro.ThreadPool$TestTask@14ae5a5 rejected from java.util.concurrent.ThreadPoolExecutor@7f31245a[Running, pool size = 4, active threads = 4, queued tasks = 3, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at com.bamboolmc.threadpro.ThreadPool.main(ThreadPool.java:46)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
1527002190243pool-1-thread-3 完成一个任务,编号为t8
1527002190243pool-1-thread-1 完成一个任务,编号为t6
1527002190243pool-1-thread-2 完成一个任务,编号为t5
1527002190243pool-1-thread-4 完成一个任务,编号为t7
1527002190749pool-1-thread-2 完成一个任务,编号为t3
1527002190749pool-1-thread-1 完成一个任务,编号为t3
1527002190749pool-1-thread-3 完成一个任务,编号为t4
对比调用策略5

修改如下

    private static int taskSize = 3;
    private static int corePoolSize = 0;
    private static int maximumPoolSize = 4;
    private static int queueSize = 3;

任务数量3, 核心线程数量0,任务队列3,非核心线程4
运行结果:

1527002486575pool-1-thread-1 完成一个任务,编号为t3
1527002487078pool-1-thread-1 完成一个任务,编号为t2
1527002487579pool-1-thread-1 完成一个任务,编号为t1

线程池分类

根据构建线程池时传入的参数不同,我们在Android系统中常见有4种不同功能特性的线程池。

  • CachedThreadPool
  • FixedThreadPool
  • SingleThreadExecutor
  • ScheduledThreadPool

我们可以用Executors.newXXX的方式去实例化我们需要的线程池,如实例化一个CachedThreadPool

 ExecutorService executorService =  Executors.newCachedThreadPool();

CachedThreadPool

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

核心线程数:0
最大线程数:2的31次方-1
超时时长:60秒
任务队列:SynchronousQueue
  由此可见,CachedThreadPool只有非核心线程,并且非核心线程最大数量可以任意大,当前已建线程都在执行任务时,再来新的任务会开启新的线程来执行,闲置线程超过60秒,就会被收回。由于使用了SynchronousQueue队列,所有提交的任务都会被立即执行。这类线程池比较适合大量的耗时较少的任务。

FixedThreadPool

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

核心线程数:固定(新建线程池时提供的参数)
最大线程数:同核心线程数
超时时长:0
任务队列:LinkedBlockingQueue
  由此可见,FixedThreadPool只有固定个数的核心线程数,无非核心线程,并且核心线程无超时限制,除非线程池关闭,否则线程即使处于空闲状态,也不会被回收,因此能够更快的响应外界请求。另外任务队列容量没有大小限制。当所有线程均有任务执行时,新任务会在任务队列排队,线程执行完,取出任务队列中任务执行。

SingleThreadExecutor

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }

核心线程数:1
最大线程数:1
超时时长:0
任务队列:LinkedBlockingQueue
  由此可见,SingleThreadExecutor只有1个核心线程,所有任务都会在一个线程中按顺序执行;任务队列容量没有大小限制;另外如果某些错误而导致线程终止,则会创建新的线程继续执行后续的任务(与newFixedThreadPool(1)的不同,无法创建新的线程继续执行后续任务);由于是在一个线程执行任务,所以无需处理线程同步问题了。

ScheduledThreadPool

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

核心线程数:固定
最大线程数:2的31次方-1
超时时长:10毫秒
任务队列:DelayedWorkQueue
  由此可见,ScheduledThreadPool核心线程数固定,非核心线程数无限大,非核心线程超时时长10毫秒。主要用于执行定时任务和具有固定周期的任务。

使用方法异同

ExecutorService executor =  Executors.newCachedThreadPool();
executor.execute(new TestTask());
ExecutorService executor =  Executors.newFixedThreadPool(2);
executor.execute(new TestTask());
ExecutorService executor =  Executors.newSingleThreadExecutor();
executor.execute(new TestTask());

ScheduledExecutorService executor =  Executors.newScheduledThreadPool(2);
 executor.schedule(new TestTask(),2000,TimeUnit.MILLISECONDS);

任务队列BlockingQueue

  阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。

  • 支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满时,自动唤醒被阻塞线程继续执行插入操作。
  • 支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会阻塞,等待队列变为非空时,自动唤醒。

  四种类型线程池分析时,发现其中的任务队列参数分别使用到SynchronousQueue、LinkedBlockingQueue、DelayedWorkQueue这三种不同的任务队列。除此之外,系统还提供了PriorityBlockingQueue、ArrayBlockingQueue,DelayQueue,这些类均实现自BlockingQueue接口。

public interface BlockingQueue<E> extends Queue<E> {
    boolean add(E e);
    boolean offer(E e);
    void put(E e) throws InterruptedException;
    boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
    E take() throws InterruptedException;
    E poll(long timeout, TimeUnit unit) throws InterruptedException;
    int remainingCapacity();
    boolean remove(Object o);
    public boolean contains(Object o);
    int drainTo(Collection<? super E> c);
    int drainTo(Collection<? super E> c, int maxElements);
}

BlockingQueue作为一个接口,提供了添加、删除元素方法。

添加方法

  • add: 添加元素到队列,成功返回true;队列容量满了,再添加元素时,会抛出IllegalStateException异常。
  • offer:添加元素到队列,成功返回true,失败返回false
  • put:添加指定元素到队列,如果队列容量满了,则会阻塞到队列有空间。

删除方法

  • take:删除队列头部元素,如果队列为空,则一直阻塞到队列有元素为止。返回头部元素。
  • poll:删除队列头部元素,在指定时间内,如果队列不为空,则删除头部元素,并返回头部元素。在制定时间内队列仍为空,则返回null。
  • remove:删除队列内指定的元素,如果删除成功则返回true,如果找不到该指定元素,抛出NullPointerException。

其他方法

  • remainingCapacity:返回队列能够添加元素的数量(阻塞或无限制除外)。
  • contains:队列是否包含某指定元素。
  • drainTo:从队列中移除(所有或指定数量的)可用元素,并将它们添加到给定集合中。

SynchronousQueue

  没有元素存储空间的阻塞队列,如果在插入元素时后续没有执行取出的操作,那么插入的行为就会被阻塞。构造方法如下

public SynchronousQueue()
public SynchronousQueue(boolean fair)

LinkedBlockingQueue

  基于链表的阻塞队列,可指定容量,默认容量无穷大(2的31次方-1),采用FIFO(first-in-first-out)的访问策略。

public LinkedBlockingQueue()
public LinkedBlockingQueue(int capacity)
public LinkedBlockingQueue(Collection<? extends E> c)

PriorityBlockingQueue

  基于优先级的阻塞队列,队列容量由使用者指定,优先级的高低是如何指定的呢,可根据如下的构造函数中看到Comparator<? super E> comparator 就是指定优先级的比较器。因此队列元素(也就是要执行的任务)需要实现Comparator接口,来定义优先级高低。如果不设置容量大小的话,默认队列容量为11。

public PriorityBlockingQueue()
public PriorityBlockingQueue(int initialCapacity)
public PriorityBlockingQueue(int initialCapacity,Comparator<? super E> comparator)
public PriorityBlockingQueue(Collection<? extends E> c)

ArrayBlockingQueue

  基于数组的阻塞队列,队列容量由使用者指定,由于队列基于数组,初始化后,容量大小就是固定的。当设定boolean fair为true时,队列访问策略为FIFO;如果不设定,则默认false,访问策略是无序的。

public ArrayBlockingQueue(int capacity)
public ArrayBlockingQueue(int capacity, boolean fair)
public ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c)

其他

详细的阻塞队列可参考源码及如下文章学习
https://blog.csdn.net/qq_38989725/article/details/73298856

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 为什么使用线程池 当我们在使用线程时,如果每次需要一个线程时都去创建一个线程,这样实现起来很简单,但是会有一个问题...
    闽越布衣阅读 4,276评论 10 45
  • 前言 线程池是Java中的一个重要概念,从Android上来说,当我们跟服务端进行数据交互的时候我们都知道主线程不...
    老实任阅读 1,244评论 1 9
  • 【JAVA 线程】 线程 进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者...
    Rtia阅读 2,758评论 2 20
  • 那一刻是最甜蜜的 可是已经找不到回头看的路,或许是走过的路程上有雾霾吧!还是风尘染遍了过去!再也找不到曾经的感觉了...
    晓行人阅读 191评论 0 0
  • 嗒哒!我又出场啦!好久不见,今天我要给大家介绍一套书—— Splat ! 《斯普拉特在...
    萧以洛阅读 565评论 2 3