1. Java 中的线程池是如何实现的
在 Java 中, 所谓的线程池中的 "线程", 其实是被抽象为了一个静态内部类 Worker, 它基于 AQS 实现, 存放在线程池的 HashSet<Worker> workers 成员变量中;
需要执行的任务则存放在成员变量 workQueue (Blocking Queue <Runnable> workQueue) 中. 这样整个线程池实现的基本思想就是: 从 workQueue 中不断取出需要执行的任务, 放在 Workers 中进行处理.
2. 创建线程池的几个核心构造参数有哪些
Java 中的线程池的创建其实非常灵活, 我们可以通过配置不同的参数, 创建出行为不同的线程池, 这几个参数包括:
corePoolSize: 线程池的核心线程数.
maximumPoolSize: 线程池运行的最大线程数.
keepAliveTime: 超过核心线程数时闲置线程的存活时间
workQueue: 任务执行前保存任务的队列, 保存由 execute 方法提交的 Runnable 任务.
3. 线程池中的线程怎么创建的? 是一开始就随着线程池的启动创建的嘛
不是, 线程池默认初始化不启动 Worker, 等待有请求时才启动.
每当我们调用 execute() 方法添加一个任务时, 线程池会做如下判断:
- 如果正在运行的线程数量小于 corePoolSize , 那么马上创建线程运行这个任务
- 如果这个任务的线程数量大于或等于 corePoolSize, 那么将这个任务放入队列
- 如果队列满了, 而且正在运行的线程数量小于 maximumPoolSize, 那么还是要创建非核心线程立刻运行这个任务
- 如果队列满了, 而且正在运行的线程数量大与或等于 maximumPoolSize, 那么线程池就会抛出异常 RejectExecutionException.
当一个线程完成任务时, 它会从队列中取出下一个任务来执行. 当线程数量大于 corePoolSize , 如果有线程的闲置时间超过 keepAliveTime 时, 这个线程就会被释放掉.
4. 既然提到可以通过配置不同参数创建出不同的线程池, 那么 Java 中默认实现好的线程池又有哪些? 比较他们的异同
SingleThreadExecutor 线程池
这个线程池只有一个核心线程在工作, 也就是相当于单线程串行执行所有任务. 如果这个唯一的线程因为异常结束, 那么会有一个新的线程来代替它. 此线程池保证所有任务的执行顺序按照任务的提交顺序执行.
- corePoolSize: 1
- maximumPoolSize: 1
- keepAliveTime: 0
- workQueue: new LinkedBlockingQueue <Runnable> (), 其缓冲队列是无界的.
FixedThreadPool 线程池
FixedThreadPool 是固定大小的线程池, 只有核心线程. 每次提交一个任务就会创建一个线程, 知道线程达到线程池的最大大小. 线程池的大小一旦达到最大值就会保持不变, 如果某个线程因为执行异常而结束, 那么线程池会补充一个新的线程池
FixedThreadPool 多数针对一些很稳定, 固定的正规并发线程, 多用于服务器
- corePoolSize: n
- maximumPoolSize: n
- keepAliveTime: 0
- workQueue: new LinkedBlockingQueue <Runnable> (), 其缓冲队列是无界的
CachedThreadPool 线程池
CachedThreadPool 是无界线程池, 如果线程池的大小超过了处理任务锁需要的线程, 那么就会回收部分空闲(60s 不执行任务) 的线程, 当任务数增加时, 此线程池又会添加新线程来处理任务.
线程池大小完全依赖于操作系统(或者说 JVM) 能够创建的最大线程数大小. workQueue 为 SynchronousQueue, 是一个缓冲区为 1 的阻塞队列.
缓存型池子通常用于执行一些生存周期很短的异步型任务.
- corePoolSize: 0
- maximumPoolSize: Integer.Max_Value
- KeepAliveTime: 60s
- workQueue: new SynchronousQueue<Runnable>(), 一个缓冲区为1的阻塞队列
ScheduledThreadPool 线程池
ScheduledThreadPool 核心线程池固定, 大小无限的线程池. 此线程池支持定时以及周期性执行任务的需求. 创建一个周期性执行任务的线程池. 如果闲置, 非核心线程池会在 DEFAULT_KEEPALIVEMILLIS 时间内回收
- corePoolSize: 0;
- maximumPoolSize: Integer.MAX_VALUE
- keepAliveTime: DEFAULT_KEEPALIVE_MILLIS
- workQueue: new DelayedWorkQueue()
5. 如何在 Java 线程池中提交线程
线程池最常用的提交任务的方法有两种:
- execute(): ExecutorService.execute 方法接收一个 Runnable 实例, 它用来执行一个任务
- submit(): ExecutorService.submit() 方法返回的是 Future 对象. 可以用 isDone() 来查询 Future 是否已经完成, 当任务完成时, 它具有一个结果, 可以调用 get() 来获取结果. 也可以不用 isDone() 进行检查就直接调用 get(), 这种情况下, get() 将阻塞, 直至结果准备就绪.