java中的线程池实现以及代码分析

为什么要使用线程池: 我的理解是线程池可以使线程复用,避免了每次线程都new一个新的线程,另外我们可以给线程池一个固定大小,从而避免了大量线程对CPU的占用。
我们看一下javaapi对线程池的描述:
线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。因此线程池大量使用与Android端处理耗时任务的开源APP,例如ImageLoader,AsyncHttpClient、等。

Java中Executors提供的线程池

java的Executors中提供了四种线程池:
newSingleThreadExecutor()
创建一个使用单个线程的线程池。
newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池
newCachedThreadPool()
创建一个可根据需要创建新线程的线程池,并可复用空闲线程。
newScheduledThreadPool(int corePoolSize)
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。即可用作定时处理任务。
这四个方法最终都会返回给我们一个线程池,只不过所传线程池的参数有些不同,因此我们这里先看下ThreadPoolExecutor的构造方法,然后再分析Executors是如何创造出这四种线程池的。

ThreadPoolExecutor构造方法及关键代码

ThreadPoolExecutor的构造方法有以下4个:
这里写图片描述
其实它们最后都是调用了最后7个参数的构造方法,只不过如果我们不设置ThreadFactory或者RejectedExecutionHandler会传入默认的值而已。
那么我们这里看下各参数的意义:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

corePoolSize maximumPoolSize

ThreadPoolExecutor 将根据 corePoolSize 和 maximumPoolSize设置的边界自动调整池大小。当新任务在方法 execute() 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当等待队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。
这里我们看下execute()方法代码:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    //如果当前工作线程小于corePoolSize直接添加到工作线程中
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //如果工作线程已满,则添加到等待队列中,如果线程池通知被shutdown则通过reject方法将此runable返回给参数handler处理。
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //如果等待队列已满,则将线程池添加到工作队列中,这时的判断条件为是否超过maximumPoolSize,如果超过,则reject。
    else if (!addWorker(command, false))
        reject(command);
}

* keepAliveTime 和 unit *

这两个参数规定了空闲线程所能存活的最大时间。
下面时api1.7的描述,已经比较详细的说明了参数的意义:
如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止。这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime() 动态地更改此参数。使用 Long.MAX_VALUE TimeUnit.NANOSECONDS 的值在关闭前有效地从以前的终止状态禁用空闲线程。默认情况下,保持活动策略只在有多于 corePoolSizeThreads 的线程时应用。但是只要 keepAliveTime 值非 0, allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。

threadFactory

使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从 newThread 返回 null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。

workQueue 排队队列

所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

handler

当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被 拒绝。在以上两种情况下, execute 方法都将调用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四种预定义的处理程序策略:
在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。
在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。
在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。
定义和使用其他种类的 RejectedExecutionHandler 类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。

一般情况下,我们只关心
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue
这5个参数。
尤其是合适的corePoolSize和maximumPoolSize,因为这两个决定了我们线程池的性能以及CPU占用。
通过workQueue我们可以控制多个线程竞争排队时的规则算法。
同时我们可以通过传入合适的ThreadFactory来控制线程的优先级,例如在ImageLoader中通过如下代码创建了一个用户可控制线程优先级的线程池,并且多个任务同时加载图片时顺序为后进先出。这里设置为后进先出我的理解为最后提交的图片加载极大可能为当前用户正在交互的界面,因此这样设置。

public static Executor createExecutor(int threadPoolSize, int threadPriority,
QueueProcessingType tasksProcessingType) {

    BlockingQueue<Runnable> taskQueue =
            lifo ? new LIFOLinkedBlockingDeque<Runnable>() : new LinkedBlockingQueue<Runnable>();

    return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 0L, TimeUnit.MILLISECONDS, taskQueue,
            createThreadFactory(threadPriority, "uil-pool-"));
}

Executors提供的线程池实现原理

通过上面线程池的参数讲解以及ImageLoader的例子我们已经知道了如何去自己new一个线程池,而大部分情况下,java Executors类提供的线程池已经足够我们开发了,刚开始我们就提到了executors所提供的4种线程池,那么我们看下它们是如何实现的

  1. newSingleThreadExecutor()实现如下:
    new ThreadPoolExecutor(1, 1,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue())
    我们通过代码就可以很清晰的分析出,这个线程池定长为1,并且通过线性队列控制等待线程,因此这个线程池每次只会有一个线程在执行,并且添加的线程会循序执行。因这里LinkedBlockingQueue调用无参构造方法,所以等待队列大小为Integer.MAX_VALUE。
  2. newCachedThreadPool()
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
    60L, TimeUnit.SECONDS,
    new SynchronousQueue()
    newCachedThreadPool工作队列可以允许多个线程并发执行,因其最大值设为Integer.MAX_VALUE,并且SynchronousQueue是一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。因此我们可以理解为该线程池中所有提交的线程都会同时执行,因其coreSize为0,所以我们可以复用已执行过的空闲线程。
  3. newFixedThreadPool
    return new ThreadPoolExecutor(nThreads, nThreads,
    0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue()
    其和newSingleThreadExecutor只是coreSize和maxSize参数不同,原理一样。
  4. newScheduledThreadPool
    return new ScheduledThreadPoolExecutor(corePoolSize);
    这里ScheduledThreadPoolExecutor是ThreadPoolExecutor的字类,它拓展了类似于定时任务的功能,这边博客暂时就不分析了。

总结

其实到这里线程池已经分析的差不多了。但还有线程池的状态以及一些常用方法没有写到,但在java api中已经十分详细,因此只需要多读api,多使用基本上就对线程池比较熟悉了。而且android很多优秀的开源框架中都有对线程池的使用,大家可以多读读优秀的开源库!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值