Android中的线程池(二)

上一篇博客Android中的线程池(一),简单分析了线程池的内部工作的过程,有兴趣的同学可以去阅读下。那真的是简单分析,因为在那篇文章里,只从一个任务从提交到被执行的过程简单分析。事实上线程池的内部实现原理是挺复杂的。
这篇博客介绍各种线程池。等我哪一天真正完全理解了线程池的内部每一个细节的实现,会写一篇完整的源码分析文章。

线程池说白了就是用于管理线程的。他的作用总结如下:

1. 复用线程池中的线程,避免了大量的线程创建和销毁带来的性能开销
2. 有效控制线程池的最大并发线程数,避免大量的线程之间因为抢占竞争系统的资源而阻塞
3. 提供定时执行,并发数控制等功能

线程池都实现了ExecutorService接口,该接口的实现类有ThreadPoolExecutor,ScheduledThreadPoolExecutor。线程池中有线程,并且封装了任务队列。

从api开始分析。

ThreadPoolExecutor

ThreadPoolExecutor的构造方法如下:

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

corePoolSize:核心线程数
如果线程池中的线程数量,未达到核心线程数量,那么就会开启一个核心线程来执行任务

maximumPoolSize:线程池中所能容纳的最大线程数
如果任务队列已经满了,无法插入了,此时如果线程数量还没有达到线程池的最大值,那么会启动一个非核心线程来执行任务。

keepAliveTime:超时时长
非核心线程处于闲置状态如果超过了这个时长,就会被回收,当然核心线程也是可以被回收的(核心线程默认会在线程池中一直存活)。如果我们将allowCoreThreadTimeOut属性设为true的话,核心线程处于闲置状态超过了keepAliveTime设定的时长,也会被回收。可以看下allowCoreThreadTimeOut的注释说明

/**
 * If false (default), core threads stay alive even when idle.
 * If true, core threads use keepAliveTime to time out waiting
 * for work.
 */
private volatile boolean allowCoreThreadTimeOut;

unit:超时时长keepAliveTime的单位

workQueue:任务队列
如果线程池中的线程数量已经达到,或者已经超过核心线程数量,那么任务会被插入到任务队列,排队等待执行。

threadFactory:线程工厂
通常不需要用到它

Handler:拒绝策略
如果线程数量已经达到线程池规定的最大值了,那么就拒绝执行此任务。

好了,详细介绍了各个参数。我们先总结一下线程池:
提交给线程池的任务,会优先被核心线程执行,如果核心线程全部都在运行状态,无闲置核心线程了,那么新的任务会进入任务队列排队等待,遵循先进先出的原则。当任务队列也已经满了,但是还有新的任务进来,那么分两种情况
1. 如果线程池还没满(还没达到最大线程数量,还能装入线程),此时会启动非核心线程来执行任务,非核心线程执行完任务,处于闲置状态后,遵循超时策略,超出时长会被回收。所以不必担心线程数量一直在积压
2. 如果线程池已经满了,装不下了,那么会有拒绝策略,拒绝新的任务进来

继续前进。
通过配置ThreadPoolExecutor的构造方法的参数,可以实现很多种线程池。我们的前辈们已经帮我们分好类了。上一篇也提过,线程池通过工厂方法来创建,我们最常用到的线程池是FixedThreadPool,那就先介绍他了。

1.FixedThreadPool
只需要简单的一行代码就能完成FixedThreadPool的创建工作

ExecutorService executorService = Executors.newFixedThreadPool(6);

我们就这样创建了容纳恒定6个线程的线程池
newFixedThreadPool方法如下:

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

其实就是内部调用ThreadPoolExecutor的构造方法嘛,然后配置一些参数,所以说ThreadPoolExecutor很核心。
可以看到核心线程数和最大线程数都是nThreads,超时时长为0毫秒,任务队列为无界任务队列。
可以先看看无界的任务队列LinkedBlockingQueue

public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}
/**
 * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
 *
 * @param capacity the capacity of this queue
 * @throws IllegalArgumentException if {@code capacity} is not greater
 *         than zero
 */
public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalArgumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}

这种任务队列基于链表结构,大小是没有限制的。也就是说根本不会满

所以我们可以得出结论,FixedThreadPool中只有固定数量的核心线程,并且没有超时机制,所以即使是闲置也不会被回收,而且任务队列大小没有限制,可以一直排着长队。

2.CachedThreadPool
同样只需要一行代码就能完成CachedThreadPool的创建

ExecutorService executorService = Executors.newCachedThreadPool();

这是一个很神奇的线程池,同样看到其内部ThreadPoolExecutor的参数配置,就能了解这种线程池的功能了

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

他没有核心线程数的概念,也就是说他只有非核心线程。并且最大线程数是非常大的数,可以认为线程数量不定,可以任意大。幸好还有超时时长,超过了60秒的闲置线程会被回收。。不然我同时有100个任务,那就要创建100个线程了并且 还在不断的积累数量。当然这种线程如果我一个任务都没有,那么线程池中就可以一个线程都没有,全部都被回收了。为什么说这个线程池很神奇,因为他的任务队列是不能存储元素的。这样也说明了,只要有任务到达,就会立即被执行。不会放到任务队列排队。我们分析,如果任务队列不能存储新任务了,那么这种线程是适合额执行大量的耗时较少的任务。是一种用空间来换时间的做法,占用的内存大,它的并发数量越大,也就是任务越快被执行

3.SingleThreadExecutor

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

从配置可以看出,这个线程池中只有一个线程,并且是核心线程。如果很多任务,全部放在任务队列排队,然后一直在复用这个线程执行任务

至于ThreadPoolExecutor执行就很简单了,一行代码,execute方法中传进任务即可。command是Runnable类型的参数。

executorService.execute(command);

当然也可以像上一篇博客写的那样用submit方法,submit是可以返回future对象,future可以对任务的执行结果进行获取等。这个看需求。
可以尝试提交100个任务进入线程池,每个任务休眠2秒模拟一下。这样就能感受线程池了。

ScheduledThreadPoolExecutor

从名字上看,这种线程池是可以用于执行定时任务的。我们可以这样使用:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(6);

//定时执行,1000ms后执行command任务
scheduledExecutorService.schedule(command,1000, TimeUnit.MILLISECONDS);

//周期重复执行,先延时10ms,然后每隔1000ms执行一次command任务
scheduledExecutorService.scheduleAtFixedRate(command,10,1000,TimeUnit.MILLISECONDS);

newScheduledThreadPool方法如下:

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

跟进ScheduledThreadPoolExecutor

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

核心线程数固定,但是可以容纳任意多个线程。同样的有超时时长

private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;

关于scheduleAtFixedRate方法,参数一是传入执行的任务,参数二是第一次运行任务时的延迟时间,参数三是定时任务的周期,参数四是时间单位

好了。以上就是Android中常用到的线程池。那么期待下一篇AsyncTask的实现原理

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值