概述
线程池(ThreadPool)是一种多线程处理形式,也就是线程的一种使用模式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。
每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。
如果某个线程在空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。
如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。
超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
线程池提供了一种限制、管理资源的策略。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
池化思想在计算机的应用也比较广泛,比如以下这些:
- 内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。
- 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
- 实例池(Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。
线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
线程池的优势主要体现在以下 4 点:
- 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
线程池的使用
Java线程池提供了两种创建形式:
1、通过Executors
类创建;
2、通过ThreadPoolExecutor
类创建。
除了newWorkStealingPool
创建方式,其他方式本质上都是通过ThreadPoolExecutor
类进行创建。
Executors
类创建
Executors
类提供了6种静态方法创建线程池,如下所示:
Executors.newFixedThreadPool
:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;Executors.newCachedThreadPool
:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;Executors.newSingleThreadExecutor
:创建单个线程数的线程池,它可以保证先进先出的执行顺序;Executors.newScheduledThreadPool
:创建一个可以执行延迟任务的线程池;Executors.newSingleThreadScheduledExecutor
:创建一个单线程的可以执行延迟任务的线程池;Executors.newWorkStealingPool
:创建一个抢占式执行的线程池(任务执行顺序不确定)【JDK 1.8 添加】。
具体使用方式
一、Executors.newFixedThreadPool
创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
1、代码示例
ExecutorService fixedService = Executors.newFixedThreadPool(2);
Runnable task = new Runnable() {
@Override
public void run() {
Log.e(TAG, "任务线程名: "+Thread.currentThread().getName());
}
};
// 执行5次
fixedService.execute(task);
fixedService.execute(task);
fixedService.execute(task);
fixedService.execute(task);
fixedService.execute(task);
执行结果:
MainActivity: 任务线程名: pool-2-thread-2
MainActivity: 任务线程名: pool-2-thread-2
MainActivity: 任务线程名: pool-2-thread-1
MainActivity: 任务线程名: pool-2-thread-2
MainActivity: 任务线程名: pool-2-thread-1
从执行结果可以看出线程只能执行固定两个线程,当执行中的两个线程还没有执行完毕,超出的线程会在队列中等待。
2、源码分析
查看newFixedThreadPool
的源码,如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到使用的是通过创建ThreadPoolExecutor
类来实现的,ThreadPoolExecutor
会在下面详情说明,从源码中可以得知:
- 核心线程数和最大线程数同时设置为nThreads;
- 最大线程数可以存活的时间为0;
- 时间单位为毫秒;
- 阻塞队列使用的是:链表有界阻塞队列。
二、Executors.newCachedThreadPool
创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。
1、代码示例
ExecutorService cachedService = Executors.newCachedThreadPool();
Runnable task = new Runnable() {
@Override
public void run() {
Log.e(TAG, "任务线程名: " + Thread.currentThread().getName());
}
};
for (int i = 0; i < 10; i++) {
cachedService.execute(task);
}
执行结果:
MainActivity: 任务线程名: pool-1-thread-1
MainActivity: 任务线程名: pool-1