JAVA之线程池详解
作者:橘左京
来源:SegmentFault 思否社区
前言
既然讲到了线程池,那我们就先聊一下线程,线程为何物,每一个程序为单独的一个进程,如QQ、网易云,那我们如何理解线程呢,其实在一个进程内至少包含一个线程来作为程序的最小执行单位,如迅雷同时下载多个文件。
线程池
为什么要使用线程池?线程池顾名思义是由多个线程所组成,作用就是减少线程的建立与销毁,与数据库连接池相同概念,为了减少连接与释放,从而降低消耗提升效率。
适用场景
很多小伙伴初识多线程,一直不明白多线程实际的应用,它到底应该应用在什么地方,其实还是要根据你的具体业务来决定是否适用多线程,比如展示详情页需要花费80ms,分八步查询了不同表的数据,每步占10ms,若是同步执行就需要顺序执行组装数据,这时如果引入了多线程来进行查询,只需要10ms多的时间返回数据。
创建方式
Executors类提供了六种不同的线程池创建方案,参数是方法默认的,最终都是通过ThreadPoolExecutor实例。这种方式虽然简单方便,但是也有弊端,在开发者不了解或无意中的使用可能会造成OOM。阿里手册也明确的规定不准直接使用Executors来创建线程池,要使用ThreadPoolExecutor去自定义线程池参数。
1.Executors
第一位登场的是Executors,我们先来展示一下它的方法,从图中可以看到,它给出了六种创建线程的方式,请小伙伴们根据两个一组顺序阅读。
1)newCachedThreadPool
可缓存型线程池。它的核心线程为0,但线程总数是Integer的最大值,意味着它是最大的。它使用SynchronousQueue来作为队列,不会保留任务,任务达到后直接创建线程,可能会造成OOM的发生。
2)newFixedThreadPool
定长线程池。是一个可以控制并发数量的线程池,若任务到达后线程全部占用会加入到队列当中。它使用的是LinkedBlockingQueue作为队列,是一个无界队列,任务会源源不断加入队列,有可能造成OOM的发生。
3)newScheduledThreadPool
计划型线程池。可以设置固定或延期执行任务,当线程空闲时,直接拿来使用,如果线程都被占用,则创建新的线程。它使用的是DelayedWorkQueue作为队列,这种队列能够保证只有到了时间才会执行任务。
4)newSingleThreadExecutor
单个线程的线程池。建立只有一个线程的线程池,若有多个任务进来,只有一个被执行,其他进入等待队列,遵循先进先出规则。它使用LinkedLockingQueue作为等待队列,存在造成OOM的可能。
5)newSingleThreadScheduledExecutor
周期型执行任务的单线程线程池。
6)newWorkStealingPool
工作窃取线程池。若有线程A、B,共分配了五个任务,A分配到了四个,B分配到一个,若B执行完任务A还没有执行完成,会主动的去A窃取任务进行执行。
2.ThreadPoolExecutor
第二位登场的是ThreadPoolExecutor,它可以自定义不同的参数来达到目的,其中包含七个参数,依次展示。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
**参数解析:
1)corePoolSize:核心线程数**
当核心线程未被全部创建,即使有空闲线程也不会复用,继续创建新的核心线程。
核心线程为空闲状态也不会回收,除非通过allowCoreThreadTimeOut(boolean value)设置为true。
可在项目初始化时调用prestartCoreThread()方法预创建线程,避免执行任务时创建线程效率低。
2)maximumPoolSize:线程总数
核心线程+临时线程的总数,是线程总数的上线,在核心线程不够用的情况下,会创建临时线程协助处理任务,临时线程空闲超出规定时间则回收。
3)keepAliveTime:超时时间
4)unit:可指定过期时间的单位是秒、时,分
5)workQueue:工作队列
若没有空闲线程,新的任务会加入到工作队列等待执行,队列又分为有界与无界队列,无界队列其实也有上限,为Integer的最大值。
6)threadFactory:线程工厂
用于实现生成线程的方式,定义是否为守护线程,主要用于设置线程名称。
7)handler:拒绝策略
当等待队列已满,就会执行拒绝策略,提供了四种方式进行选择
ThreadPoolExecutor.AbortPlicy:抛出异常,默认策略。
ThreadPoolExecutor.DiscardPolicy:直接丢弃任务,但不抛出异常。
ThreadPoolExecutor.DsicardOldestPolicy:丢弃最早的任务,将新任务加入队列。
ThreadPoolExecutor.CallerRunsPolicy:由线程池所在的的线程处理任务,自己处理自己的。
使用ThreadPoolExecutor正确创建线程池
static ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("橘左京").build();
static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(30), factory, new ThreadPoolExecutor.AbortPolicy());
public static class MyThread implements Runnable {
@Override
public void run() {
System.out.println("橘左京");
}
}
public static void main(String[] args){
MyThread myThread = new MyThread();
executor.submit(myThread);
}
调用线程执行有两个方法,submit和execute,submit的作用是可以通过return获取返回值,execute是无返回值的。
点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,扫描下方”二维码“或在“公众号后台“回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~
- END -