目录
- 一、线程池基础概念
- 1.1 什么是线程池?
- 1.2 为什么要用线程池?
- 1.3 线程池的工作原理
- 二、java中的线程池
- 2.1 线程池真正实现类 ThreadPoolExecutor
- 2.1.2 构造器
- 2.1.3 参数
- 2.1.3.1 workQueue
- 2.1.3.2 threadFactory
- 2.1.3.3 handler
- 2.2 线程池的使用
- 2.2.1 newCachedThreadPool
- 2.2.2 newFixedThreadPool
- 2.2.3 newScheduledThreadPool
- 2.2.4 newSingleThreadExecutor
- 2.3 方法对比
- 2.4 使用流程
- 三、内部原理逻辑
- 四、参考链接
一、线程池基础概念
1.1 什么是线程池?
线程池的基础概念
线程池(Thread Pool)是一种用于管理和复用线程的机制,它包含一组线程以及一些管理这些线程的方法。线程池在程序启动时创建一定数量的线程,并在需要执行任务时从线程池中获取线程来执行任务,任务执行完毕后线程并不立即销毁,而是返回线程池中等待下次任务执行。这样可以避免频繁地创建和销毁线程,提高系统的性能和效率。
1.2 为什么要用线程池?
为什么要用线程池?其实就是线程池优势在哪里?
1.
降低资源消耗
:线程的创建和销毁是一项消耗资源较大的操作,使用线程池可以减少这种开销。
2.提高响应速度
:线程池中的线程可以立即执行任务,无需等待线程创建,提高系统的响应速度。
3.控制并发线程数量
:通过线程池可以限制并发线程的数量,避免系统资源被耗尽。
4.提高系统稳定性
:合理使用线程池可以避免因为线程过多导致系统崩溃的情况发生。
1.3 线程池的工作原理
线程池具体是如何工作的?
按照一个线程的运行过程,大致可以明白线程池的工作原理:
1.
线程池初始化
:根据配置信息(如核心线程数、最大线程数、任务队列等)创建一定数量的线程,并将这些线程保存在线程池中。
2.任务提交
:当有任务需要执行时,线程池会从线程池中取出一个空闲线程来执行任务。
3.任务队列
:如果线程池中的线程都在执行任务,新的任务会被放入任务队列中等待执行。
4.线程复用
:当线程池中的线程空闲时,会从任务队列中取出任务来执行,实现线程的复用。
5.线程销毁
:根据配置的参数,当线程空闲时间超过设定的时间时,空闲线程会被销毁,以节省资源。
二、java中的线程池
2.1 线程池真正实现类 ThreadPoolExecutor
2.1.2 构造器
参数不同,但最终都会调到最下方的构造器中
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
2.1.3 参数
corePoolSize
(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。maximumPoolSize
(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。keepAliveTime
(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。unit
(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。workQueue
(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。threadFactory
(可选):线程工厂。用于指定为线程池创建新线程的方式。handler
(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
corePoolSize、maximumPoolSize、keepAliveTime、unit不用太多说明,都是数字或者true、false,根据自己的需求填充对应的参数即可。
2.1.3.1 workQueue
用于指定线程池中保存等待执行任务的阻塞队列。在多线程编程中,线程池的工作原理之一就是通过任务队列来保存待执行的任务,以便线程池中的线程可以按需取出任务执行。
常见的阻塞队列类型包括:
LinkedBlockingQueue
:基于链表实现的无界阻塞队列,当任务队列已满时会一直阻塞等待直到有空间。
ArrayBlockingQueue
:基于数组实现的有界阻塞队列,需要指定队列的容量,当队列已满时会阻塞等待。
SynchronousQueue
:不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,常用于直接传递任务。
PriorityBlockingQueue
:具有优先级的阻塞队列,根据元素的优先级顺序取出任务。
DelayQueue
:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
LinkedBlockingDeque
: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
LinkedTransferQueue
: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。
有界队列与无界队列
有界队列
:
指定了队列的最大容量,当队列已满时,新提交的任务将会被阻塞等待,直到队列中有空间可以存放任务。
有界队列可以避免无限制地接受任务导致内存溢出的风险,可以控制系统资源的使用。
使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略。
无界队列
:
无界队列没有指定固定的容量,可以不断地接受新的任务,直到系统资源耗尽。
无界队列可以保证任务不会被拒绝,但可能会导致内存溢出或系统资源耗尽的风险。
使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。
选择合适的阻塞队列类型可以根据实际场景来决定,例如需要控制任务的执行顺序、任务的优先级等。阻塞队列的选择也会影响线程池的性能和行为,因此需要根据具体需求进行选择。
2.1.3.2 threadFactory
用于创建新线程的工厂。在线程池中,当需要创建新的线程来执行任务时,会使用threadFactory来创建线程实例。
自定义threadFactory可以通过实现ThreadFactory接口来实现,重写newThread方法来创建新线程。例如:
ThreadFactory customThreadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("CustomThread-" + thread.getId());
thread.setPriority(Thread.MAX_PRIORITY);
return thread;
}
};
在实际应用中,通过自定义threadFactory可以实现一些特定的需求,如统一设置线程名称、设置线程优先级、设置线程为守护线程等。这样可以更好地管理线程池中的线程,使其符合系统需求和调优要求。
Executors 框架已经为我们实现了一个默认的线程工厂。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public static ThreadFactory defaultThreadFactory() {
return new DefaultThreadFactory();
}
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
@SuppressWarnings("removal")
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
2.1.3.3 handler
用于指定当线程池和任务队列都已满时,新的任务如何被拒绝执行的策略。在多线程编程中,当线程池无法接受新任务时,就会触发拒绝策略来处理这种情况。
常见的拒绝策略包括:
AbortPolicy
:默认的拒绝策略,会直接抛出RejectedExecutionException异常,通知调用者任务被拒绝。
CallerRunsPolicy
:将任务交给调用线程来执行,即在调用线程中执行被拒绝的任务。
DiscardPolicy
:直接丢弃被拒绝的任务,不做任何处理。
DiscardOldestPolicy
:丢弃队列中等待时间最长的任务,然后尝试重新提交新的任务。
自定义拒绝策略:可以实现RejectedExecutionHandler接口来自定义拒绝策略,根据具体需求来处理被拒绝的任务。
选择合适的拒绝策略可以根据实际需求来决定,例如希望通知调用者任务被拒绝、希望在调用线程中执行被拒绝的任务、或者希望丢弃一些任务以保证系统的稳定性等。
2.2 线程池的使用
一大堆的概念看完了?那么线程池具体该如何使用呢?
其实java内部通过Executors提供了四种线程池,而内部都是使用了ThreadPoolExecutor
2.2.1 newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
创建一个可缓存的线程池,适用于执行很多短期异步任务的场景。
如果线程池中有空闲线程,则重用空闲线程;如果没有空闲线程,则创建新线程。
当线程空闲时间超过60秒时,会被回收,适用于执行大量短期异步任务的情况。
2.2.2 newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
创建一个固定大小的线程池,适用于控制线程数量的场景。
线程池中始终保持固定数量的工作线程,当有任务提交时,如果有空闲线程则立即执行,否则任务进入队列等待。
适用于需要限制线程数量的情况,避免线程过多导致资源消耗过大。
2.2.3 newScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
创建一个固定大小的线程池,支持定时及周期性任务执行。
可以设置核心线程数,适用于需要定时执行任务的场景,如定时任务、周期性任务等。
可以通过schedule方法或scheduleAtFixedRate方法执行定时任务。
2.2.4 newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
创建一个只有一个工作线程的线程池,适用于需要顺序执行任务的场景。
线程池中只有一个工作线程,保证任务按照提交的顺序依次执行。
适用于需要按顺序执行任务、避免并发执行的场景。
public static void main(String[] args) {
cachedThreadPoolTest();
fixedThreadPoolTest();
ScheduledExecutorServiceTest();
singleThreadExecutorTest();
}
public static void cachedThreadPoolTest() {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(() -> {
System.out.println("Task 1");
});
cachedThreadPool.execute(() -> {
System.out.println("Task 2");
});
}
public static void fixedThreadPoolTest() {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
fixedThreadPool.execute(() -> {
System.out.println("Task 1");
});
fixedThreadPool.execute(() -> {
System.out.println("Task 2");
});
}
public static void ScheduledExecutorServiceTest() {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
scheduledThreadPool.schedule(() -> {
System.out.println("Scheduled Task 1");
}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(() -> {
System.out.println("Scheduled Task 2");
}, 0, 5, TimeUnit.SECONDS);
}
public static void singleThreadExecutorTest() {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(() -> {
System.out.println("Task 1");
});
singleThreadExecutor.execute(() -> {
System.out.println("Task 2");
});
}
2.3 方法对比
方法 | 特点 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
newCachedThreadPool | 线程池中的线程数量可以动态调整。 | 能够根据实际需求动态调整线程数量,节省资源。 | 可能会因为线程数量过多导致系统资源消耗过大。 | 适用于执行大量短期异步任务的场景。 |
newFixedThreadPool | 线程池中的线程数量是固定的。 | 能够限制线程数量,避免线程过多导致资源消耗过大。 | 可能会因为线程数量有限而无法满足高并发需求。 | 适用于控制线程数量的场景。 |
newScheduledThreadPool | 支持定时及周期性任务执行,可以设置核心线程数,最大线程数和线程空闲时间等参数。 | 能够方便地执行定时任务、周期性任务等。 | 可能会因为定时任务过多导致线程池负载过重。 | 适用于需要定时执行任务的场景。 |
newSingleThreadExecutor | 只有一个工作线程的线程池 | 能够保证任务按照提交的顺序依次执行,避免并发执行。 | 可能会因为只有一个线程而无法满足高并发需求。 | 适用于需要顺序执行任务的场景。 |
2.4 使用流程
// 1. 创建线程池
// 创建时,通过配置线程池的参数,从而实现自己所需的线程池
Executor threadPool = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory
);
// 注:在Java中,已内置4种常见线程池,下面会详细说明
// 2. 向线程池提交任务:execute()
// 说明:传入 Runnable对象
threadPool.execute(new Runnable() {
@Override
public void run() {
... // 线程执行任务
}
});
// 3. 关闭线程池shutdown()
threadPool.shutdown();
// 关闭线程的原理
// a. 遍历线程池中的所有工作线程
// b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)
// 也可调用shutdownNow()关闭线程:threadPool.shutdownNow()
// 二者区别:
// shutdown:设置 线程池的状态 为 SHUTDOWN,然后中断所有没有正在执行任务的线程
// shutdownNow:设置 线程池的状态 为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
// 使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()
三、内部原理逻辑
此图来自大神的简书
Android多线程:线程池ThreadPool全面解析
四、参考链接
Android多线程:线程池ThreadPool全面解析
java线程池使用最全详解
Java 多线程:彻底搞懂线程池