Android中的线程池(二)

来源:互联网 发布:淘宝小视频保存到手机 编辑:程序博客网 时间:2024/06/16 20:44

上一篇博客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
原创粉丝点击