浅谈线程池
1、线程池
1.1、线程池介绍
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
1.2、线程池的优点
线程池有如下优势:
- 避免频繁创建和销毁线程带来的资源浪费
- 无需创建和销毁线程,提升响应速度
- 提高线程的可管理性
1.3、创建线程池的方式
创建线程的方式有如下两种:
- ThreadPoolExecutor
- Executors
2、ThreadPoolExecutor
点进ThreadPoolExecutor 构造方法有四个,但是前面三个都是在最后一个上进行的包装处理,所以这里只针对最后一个构造方法进行说明。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
先聊参数:
- corePoolSize: 线程池中的核心线程数,默认情况下核心线程一直存活在线程池中,如果将ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设为 true,如果线程池一直闲置并超过了 keepAliveTime 所指定的时间,核心线程就会被终止。
- maximumPoolSize: 最大线程数,当线程不够时能够创建的最大线程数(包含核心线程数)
临时线程数 = 最大线程数 - 核心线程数
-
keepAliveTime: 线程池的闲置超时时间,默认情况下对非核心线程生效,如果闲置时间超过这个时间,非核心线程就会被回收。如果 ThreadPoolExecutor 的 allowCoreThreadTimeOut 设为 true 的时候,核心线程如果超过闲置时长也会被回收。
-
unit: 配合 keepAliveTime 使用,用来标识 keepAliveTime 的时间单位,一般使用TimeUnit中的枚举常量,解释如下:
TimeUnit.DAYS //天
TimeUnit.HOURS //小时
TimeUnit.MINUTES //分钟
TimeUnit.SECONDS //秒
TimeUnit.MILLISECONDS //毫秒
TimeUnit.NANOSECONDS //毫微秒
TimeUnit.MICROSECONDS //微秒 -
workQueue: 线程池中的任务队列,使用 execute() 或 submit() 方法提交的任务都会存储在此队列中。
ThreadPoolExecutor线程池推荐了三种等待队列,它们是:SynchronousQueue 、LinkedBlockingQueue和 ArrayBlockingQueue。
-
SynchronousQueue :一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于 阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
-
LinkedBlockingQueue:基于链表结构的无界阻塞队列,它可以指定容量也可以不指定容量(实际上任何无限容量的队列/栈都是有容量的,这个容量就是Integer.MAX_VALUE)
-
ArrayBlockingQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。
-
-
threadFactory: 为线程池提供创建新线程的线程工厂。如果不指定线程工厂时,ThreadPoolExecutor 会使用ThreadPoolExecutor.defaultThreadFactory 创建线程。默认工厂创建的线程:同属于相同的线程组,具有同为 Thread.NORM_PRIORITY 的优先级,以及名为 “pool-XXX-thread-” 的线程名(XXX为创建线程时顺序序号),且创建的线程都是非守护进程。
-
handler:表示当 workQueue 已满,且池中的线程数达到 maximumPoolSize 时,线程池拒绝添加新任务时采取的策略。(可以不指定)
ThreadPoolExecutor 也提供了 4 种默认的拒绝策略:
- DiscardPolicy():丢弃掉该任务但是不抛出异常,不推荐这种(导致使用者没觉察情况发生)
- DiscardOldestPolicy():丢弃队列中等待最久的任务,然后把当前任务加入队列中。
- AbortPolicy():丢弃任务并抛出 RejectedExecutionException 异常(默认)。
- CallerRunsPolicy():由主线程负责调用任务的run()方法从而绕过线程池直接执行,既不抛弃 任务也不抛出异常(当最大线程数满了,任务队列中也满了,再来一个任务,由主线程执行)
示例:
// 新建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5,
10,
5,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(5),
new ThreadPoolExecutor.AbortPolicy()
);
// execute的使用
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("hello, world!");
}
});
// submit的使用
Future<String> submit = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("hello, world!");
return "hello, world!";
}
});
System.out.println(submit.get());
另外,ThreadPoolExecutor 还提供了许多的get()/set()方法
3、Executors
Executors 执行器创建线程池很多基本上都是在 ThreadPoolExecutor 构造方法上进行简单的封装,特殊场景根据需要自行创建。可以把Executors理解成一个工厂类。Executors可以创建6 种不同的线程池类型。
下面对这六个方法进行简要的说明:
-
newFixedThreadPool: 创建一个数量固定的线程池,超出的任务会在队列中等待空闲的线程,可用于控制程序的最大并发数。
// 只需要传入一个参数,该参数将被用作核心线程数以及最大线程数,其他参数将采用默认选项 public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
-
newCacheThreadPool: 短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并试图缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存。如果现有线程没有可用的,则创建一个新线程并添加到池中,如果有被使用完但是还没销毁的线程,就复用该线程。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
-
newScheduledThreadPool: 创建一个数量固定的线程池,支持执行定时性或周期性任务。
// corePoolSize必传,threadFactory public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); }
-
newWorkStealingPool: Java 8 新增创建线程池的方法,创建时如果不设置任何参数,则以当前机器CPU 处理器数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。
public static ExecutorService newWorkStealingPool() { return new ForkJoinPool (Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true); }
-
newSingleThreadExecutor: 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
// 可以指定线程工厂,也可以传入任何参数,将采用默认的线程工厂Executors.defaultThreadFactory() public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
-
newSingleThreadScheduledExecutor: 此线程池就是单线程的newScheduledThreadPool。
// // 可以指定线程共从,也可以传入任何参数,将采用默认的线程工厂Executors.defaultThreadFactory() public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1, threadFactory)); }
需要注意的是,在阿里巴巴Java开发手册中有这样的一条规定
也就是说,在实际上产中,应该尽量的避免用Excutors直接创建线程。