- 常用多线程
//创建固定核心数的线程池,这里核心数 = 2
ExecutorService executor02 = Executors.newFixedThreadPool(2);
- 使用方式及参数配置详解
- Executors.newFixedThreadPool(2);
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue. At any point, at most
* {@code nThreads} threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks. The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
*
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
/**
* 创建一个线程池,重用固定数量的线程操作一个共享的无界队列。在任何时候,最多{@code nThreads}线程将是活动的处理任务。
* 如果所有线程都处于活动状态时提交了额外的任务,它们将在队列中等待,直到有线程可用。
* 如果任何线程在执行过程中由于失败而终止在关闭前,如有需要,将会有一个新的取代它的位置执行后续任务。池中的线程将存在
* 直到显式地{@link ExecutorService#shutdown shutdown}。
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//停止接收新的任务,并继续完成正在执行的任务和队列中的任务
ExecutorService#shutdown
//等所有已提交的任务(包括正在跑的和队列中等待的)执行完
//或者等超时时间到
//或者线程被中断,抛出InterruptedException
ExecutorService#awaitTermination(1, TimeUnit.HOURS);
//停止接受新的任务,忽略队列中的任务并尝试终止正在执行的任务
ExecutorService#shutdownNow
- newFixedThreadPool的特点:
- 创建的线程数量固定。
- 创建的线程可以重复使用。
- 提交一个任务,就创建一个线程,直到达到线程池的最大容量。
- 有执行异常结束的线程,线程池会补充一个新的线程。
- 使用无边界的队列来存储需要执行的任务。
- 自动回收不使用的线程(终止并从缓存中移除那些已有 60 秒钟未被使用的线程),(在无可用线程的情况下)自动的为新来的task创建新线程。
- 使用完成,需要手动关闭线程池。
- 阻塞队列容量大,当队列中推积的数据过多会造成CPU内存过高异常,导致程序异常退出。
简单介绍:
-
newFixedThreadPool内部有个任务队列,假设线程池里有3个线程,提交了5个任务,那么后两个任务就放在任务队列了,即使前3个任务sleep或者堵塞了,也不会执行后两个任务,除非前三个任务有执行完的。
-
如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
问题:
- task被提交都了LinkedBlockingQueue中。这里有个问题,如果任务列表很大,一定会把内存撑爆,如何解决?
- 可以看出没设置大小,则为Integer.MAX_VALUE
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
期待有好的解决方案,目前解决方案是通过线程池,阿里代码规范:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
public static void main(String[] args) throws IOException, InterruptedException {
//核心线程数
int corePoolSize = 3;
//最大线程数
int maximumPoolSize = 6;
//超过 corePoolSize 线程数量的线程最大空闲时间
long keepAliveTime = 2;
//以秒为时间单位
TimeUnit unit = TimeUnit.SECONDS;
//创建工作队列,用于存放提交的等待执行任务
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(2);
ThreadPoolExecutor threadPoolExecutor = null;
try {
//创建线程池
threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
new ThreadPoolExecutor.AbortPolicy());
//循环提交任务
for (int i = 0; i < 8; i++) {
//提交任务的索引
final int index = (i + 1);
threadPoolExecutor.submit(() -> {
//线程打印输出
System.out.println("大家好,我是线程:" + index);
try {
//模拟线程执行时间,10s
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//每个任务提交后休眠500ms再提交下一个任务,用于保证提交顺序
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}
- 执行的流程:
1. 首先通过 ThreadPoolExecutor 构造函数创建线程池;
2. 执行 for 循环,提交 8 个任务(恰好等于maximumPoolSize[最大线程数] + capacity[队列大小]);
3. 通过 threadPoolExecutor.submit 提交 Runnable 接口实现的执行任务;
4. 提交第1个任务时,由于当前线程池中正在执行的任务为 0 ,小于 3(corePoolSize 指定),所以会创建一个线程用来执行提交的任务1;
5. 提交第 2, 3 个任务的时候,由于当前线程池中正在执行的任务数量小于等于 3 (corePoolSize 指定),所以会为每一个提交的任务创建一个线程来执行任务;
6. 当提交第4个任务的时候,由于当前正在执行的任务数量为 3 (因为每个线程任务执行时间为10s,所以提交第4个任务的时候,前面3个线程都还在执行中),此时会将第4个任务存放到 workQueue 队列中等待执行;
7. 由于 workQueue 队列的大小为 2 ,所以该队列中也就只能保存 2 个等待执行的任务,所以第5个任务也会保存到任务队列中;
8. 当提交第6个任务的时候,因为当前线程池正在执行的任务数量为3,workQueue 队列中存储的任务数量也满了,这时会判断当前线程池中正在执行的任务的数量是否小于6(maximumPoolSize指定);
9. 如果小于 6 ,那么就会新创建一个线程来执行提交的任务 6;
10. 执行第7,8个任务的时候,也要判断当前线程池中正在执行的任务数是否小于6(maximumPoolSize指定),如果小于6,那么也会立即新建线程来执行这些提交的任务;
11. 此时,6个任务都已经提交完毕,那 workQueue 队列中的等待 任务4 和 任务5 什么时候执行呢?
12. 当任务1执行完毕后(10s后),执行任务1的线程并没有被销毁掉,而是获取 workQueue 中的任务4来执行;
13. 当任务2执行完毕后,执行任务2的线程也没有被销毁,而是获取 workQueue 中的任务5来执行;
通过上面流程的分析,也就知道了之前案例的输出结果的原因。其实,线程池中会线程执行完毕后,并不会被立刻销毁,线程池中会保留 corePoolSize 数量的线程,当 workQueue 队列中存在任务或者有新提交任务时,那么会通 过线程池中已有的线程来执行任务,避免了频繁的线程创建与销毁,而大于 corePoolSize 小于等于 maximumPoolSize 创建的线程,则会在空闲指定时间(keepAliveTime)后进行回收。