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

来源:互联网 发布:linux ftp 断点续传 编辑:程序博客网 时间:2024/06/06 00:30

为什么要使用线程池: 我的理解是线程池可以使线程复用,避免了每次线程都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 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 还在气头上时孩子就来撒娇了怎么办 老公把老婆微信屏蔽了老婆该怎么办 魅族手机有质量问题不给退款怎么办 屏幕点不动锁屏密码无法点怎么办 在嘴巴和鼻翼周围长痘该怎么办 小孩嘴巴被蚊虫咬的红肿怎么办 每次洗头都掉好多头发该怎么办 关于宝宝脾不好胃口却很好怎么办 高中生掉发严重怎么办该看什么科 脱头发如何治疗 掉头发厉害怎么办 头发上突然秃了一小块怎么办 全秃过后长出来白色头发怎么办 高三学生喜欢打球影响学习怎么办 高三学生受同学搔挠学习怎么办 烫完头发掉头发很厉害怎么办 烫头发后掉头发很厉害怎么办 十八岁了下门牙活动疼痛怎么办 烫完头发后掉头发很厉害怎么办 16岁的孩孑得了肺炎怎么办 我的头发掉的很厉害怎么办 八个月宝宝头后面没头发怎么办 生完孩子掉头发很厉害怎么办 生完孩子后掉头发很厉害怎么办 头发又细又软又卷怎么办 后颈部没有头发掉光了想植发怎么办 头发掉的厉害怎么办怎么拯救掉头发 头发可以种植吗 如果是秃顶怎么办 染头发把手指甲染黑了怎么办 怀孕两个月下体流褐色分泌物怎么办 头发总是大把大把的得掉 怎么办 严重脱发怎么办去问南宁肤康 脱发严重怎么办去看南宁肤康 前额头发少怎么办 如何使头发增多 生完宝宝头发一把一把的掉怎么办 生完宝宝后头发掉的厉害怎么办 生完宝宝头发掉的厉害怎么办 生了小孩后头发掉很多怎么办 生了孩子头发掉的很厉害怎么办 母乳期头发掉的很厉害怎么办 宝宝吃母乳头发掉的厉害怎么办 头发油腻头皮屑多还掉头发怎么办