Java并发编程系列——线程池
之前写了线程和锁,例子中采用直接创建线程的方式,这种方式做示例可以,但在实际生产环境中比较少用,通常会使用线程池。
使用线程池有一些明显的好处,可以考虑我们使用连接池的情形,不难想像。使用线程池可以免去我们手动创建和销毁线程的工作,节省这部分资源的消耗,提高响应速度,同时线程由线程池维护,也提高了线程的可管理性。
JDK中默认实现了多种线程池,如FixedThreadPool,SingleThreadExecutor,CachedThreadPool,ScheduledThreadPool,SingleThreadScheduledExecutor,WorkStealingPool。ForkJoinPool也是线程池的一种,通常我们单独讨论,之前的文章有所介绍。
线程工厂
线程池的创建方法使用线程池的工厂类Executors,调用相应的方法创建相应的线程池。创建线程池的工作即实例化ThreadPoolExecutor,所以有必要简单看下ThreadPoolExecutor。
ThreadPoolExecutor的构造参数:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:
* {@code corePoolSize < 0}
* {@code keepAliveTime < 0}
* {@code maximumPoolSize <= 0}
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
对如上参数简单说明:
任务的执行
用线程池执行任务有两种方式,
线程池的停止
关闭线程池使用
接下来简单介绍下几个常见线程池。
corePoolSize等于maximumPoolSize,阻塞队列使用了LinkedBlockingQueue,但并未初始化其容量,可以认为相当于使用了无界队列(为什么说到无界,文章最后会提到)。适用于对服务器负载有严格控制的场景。
corePoolSize等于maximumPoolSize等于1,阻塞队列同样使用了未初始化容量的LinkedBlockingQueue,为无界队列。适用于对任务执行顺序有要求的场景。
corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE,使用的队列为SynchronousQueue,同样为无界。该线程池会根据需要创建新线程,适用于执行时间非常短的数量较多的异步任务。
其maximumPoolSize为Integer.MAX_VALUE,队列使用了DelayedWorkQueue,同样为无界。适用于需要定期执行任务的场景。
corePoolSize为1,队列同样使用了DelayedWorkQueue,为无界。适用于需要定期按顺序执行任务的场景。
工作密取队列,内部使用了ForkJoinPool,但使用了默认工厂创建,同样为无界形式。
线程池使用示例
通过一段代码简单看下线程的使用,以SingleThreadExecutor为例。
public class ShowSingleExecutor {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
System.out.println("executed 1 by single thread");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
executorService.execute(() -> {
System.out.println("executed 2 by single thread");
}
);
executorService.shutdown();
}
}
该示例将演示使用单线程线程池,其第一个任务执行5秒后才会执行第二个任务。
其他线程池文章中鉴于篇幅不再举例。
自定义线程池
最后说一下之前提到的无界问题。无界意味着如果出现处理不够及时的情况时,任务会逐渐堆积,而造成服务不可用或服务崩溃。所以在实际使用中通常需要自定义ThreadPoolExecutor,并在内部使用有界队列的方式或通过其他手段达到类似有界的效果。对于队列满时的饱和策略除了文中介绍的四种实现,同样可以根据实际情况自定义。
欢迎关注公众号“像什么”

本系列其他文章: