Java 多线程(九)- 理解线程池

线程池

合理利用线程池能够带来以下好处:

  • 降低消耗。通过重复利用已创建的线程降低创建和销毁线程的消耗;
  • 提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行;
  • 提高稳定性。如果无限制的创建线程,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

为了使线程池的性能达到最优,需要注意:

  • 任务时同类型的,并且相互独立;因为当在线程池中执行独立的任务时,可以随意的改变线程池的大小和配置,这些修改只会对执行性能产生影响。而且如果提交的任务依赖其他任务,很可能产生死锁。
  • 尽量避免执行时间较长的任务。如果执行时间很长的任务比较多,除非线程池很大,否则将可能造成“阻塞”,线程池的响应性会变的很糟糕。

ThreadPoolExecutor 框架

在 JUC 中,ThreadPoolExecutor 实现了一个可配置高性能的线程池。库里已经封装了一些常用的的线程池,如果现有的不能满足实际需求,也可以继承它进行修改。ThreadPoolExecutor 继承关系如下图所示:

类图

其中最核心的是 submit 接口,sumbit 内部调用 execute,无非做了一次封装,生成了 Future 对象,通过该对象可以获取任务结果。

构造函数
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler);
  • int corePoolSize

线程池的基本大小:当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

  • int maximumPoolSize

线程池最大大小,线程池允许创建的最大线程数。如果队列已满,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果

  • long keepAliveTime

线程活动保持时间:线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

  • TimeUnit unit

线程活动保持时间的单位

  • BlockingQueue<Runnable> workQueue

用于保存等待执行的任务的阻塞队列。一般选用 LinkedBlockingQueue 和 SynchronousQueue。

  • ThreadFactory threadFactory

用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助。

  • RejectedExecutionHandler handler

当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是 JDK 提供的四种策略

  1. AbortPolicy:直接抛出异常。
  2. CallerRunsPolicy:只用调用者所在线程来运行任务。
  3. DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  4. DiscardPolicy:不处理,丢弃掉。
参数与管理机制

ThreadPoolExecutor 根据 corePoolSize, maximumPoolSize,keepAliveTime 等参数管理线程。如果线程数量小于 corePoolSize,则新任务到来都会生成新的线程。如果数量大于 corePoolSize,新任务会被加入到阻塞队列中去。如果阻塞队列有界且满,同时线程数量还小于 maximumPoolSize,则会生成新的工作线程帮助完成任务。如果很不幸线程数超过 maximumPoolSize,则按照 RejectedExecutionHandler 处理新到的任务。

如果任务完成且线程处于等待状态,ThreadPoolExecutor 会根据 keepAliveTime 销毁空闲线程,直到线程数量不大于 corePoolSize。但是若调用过 allowCoreThreadTimeOut 接口,corePoolSize 下的线程也会被销毁。

线程管理机制如下图所示:


线程管理机制

常用线程池

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

corePoolSize 和 maximumPoolSize 同为 nThreads, 使用无界阻塞队列 LinkedBlockingQueue。因此线程池中线程数量固定,多余的任务会被加入无界链表阻塞队列。

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

corePoolSize 和 maximumPoolSize 同为 1, 使用无界阻塞队列 LinkedBlockingQueue。因此线程池中只有 1 条线程,多余的任务会被加入无界链表阻塞队列。

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

corePoolSize 为 0, maximumPoolSize 是 Integer 极大值,采用 SynchronousQueue 阻塞队列。也就是说若没有空闲线程,线程池会为每个任务创建一个线程。空闲线程会等待 60 秒接收新任务,若没有新任务则会被销毁。最后所有线程都会被销毁。

合理配置

如何合理配置线程池大小,仅供参考。一般需要根据任务的类型来配置线程池大小:

  • 如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1

  • 如果是IO密集型任务,参考值可以设置为2*NCPU,因为并不是所有任务都是在计算中,很多是等候 IO。

当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。

内容来源

Java 并发编程实战

http://825635381.iteye.com/blog/2184680

http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-ThreadPoolExecutor.html

http://ifeve.com/java-threadpool/

http://blog.csdn.net/zxc123e/article/details/51891200

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

推荐阅读更多精彩内容