目录
为什么要使用线程池
线程池的创建
线程池执行逻辑
线程池的关闭
线程池的工作原理
线程池阻塞队列
线程池的饱和策略
代码示例
如何配置线程池参数?
在实际使用中,线程是很占用系统资源的,如果对线程管理不善很容易导致系统问题。因此,在大多数并发框架中都会使用线程池来管理线程,使用线程池管理线程主要有以下好处:
综上所述,使用线程池可以降低资源消耗,提升系统响应速度,提高线程的可管理性,并且可以防止任务过载,同时还能提供线程管理和监控功能。这些优势使得线程池在多线程应用中被广泛使用,能够有效地提升系统的性能、效率和可靠性。
创建线程池主要使用ThreadPoolExecutor类,具有多个重载的构造方法。以下是通过最多参数的构造方法来理解创建线程池时需要配置的参数:
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
下面对每个参数进行说明:
1、corePoolSize:表示核心线程池的大小。当提交一个任务时,如果当前核心线程池中的线程数没有达到corePoolSize,即使存在空闲线程,也会创建新的线程来执行任务。如果调用了prestartCoreThread()或prestartAllCoreThreads()方法,在线程池创建时所有核心线程将被创建并启动。
2、maximumPoolSize:表示线程池能够创建的最大线程数。当阻塞队列已满时,如果当前线程池中的线程数没有超过maximumPoolSize,则会创建新的线程来执行任务。
3、keepAliveTime:空闲线程的存活时间。如果当前线程池中的线程数超过了corePoolSize,且空闲时间超过了keepAliveTime,则会销毁这些空闲线程,以降低系统资源消耗。
4、unit:时间单位,用于指定keepAliveTime的时间单位。
5、workQueue:阻塞队列,用于保存任务。可以使用 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、PriorityBlockingQueue等不同实现来作为阻塞队列。
6、threadFactory:线程工厂,用于创建线程。可以通过指定线程工厂来为每个创建出来的线程设置有意义的名称,便于调试和问题排查。
7、handler:饱和策略,用于处理线程池已达到饱和状态时的情况。当线程池的阻塞队列已满且所有线程都已经开启时,表示线程池已经饱和。可选的策略包括:
以上是使用最多参数的构造方法来创建线程池时需要配置的参数。根据具体的业务需求和性能要求,可以灵活调整这些参数来达到最佳的线程池配置。
使用execute(Runnable task)方法提交一个Runnable任务给线程池执行,或者使用submit(Callable
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果线程池的线程个数少于corePoolSize则创建新线程执行当前任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果线程个数大于corePoolSize或者创建线程失败,则将任务存放在阻塞队列workQueue中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果当前任务无法放进阻塞队列中,则创建新的线程来执行任务
else if (!addWorker(command, false))
reject(command);
}
注意:execute方法的执行逻辑包括以下几个情况:
线程池的设计思想是通过合理利用有限的资源(核心线程数和阻塞队列)来处理任务,避免频繁创建和销毁线程带来的额外开销,提高系统的性能和资源利用率。这种设计思想被广泛应用于各种框架和系统中。
shutdown()方法和shutdownNow()方法是用于关闭线程池的两个常用方法。
当调用shutdown()方法时,线程池的状态会被设置为SHUTDOWN,此时线程池将不再接受新的任务提交,但会继续执行已提交的任务,直到所有任务执行完成。该方法不会阻塞,会立即返回。
而调用shutdownNow()方法时,线程池的状态会被设置为STOP,线程池会尝试停止所有正在执行和未执行的任务,并返回等待执行的任务列表。该方法会中断所有工作线程,包括正在执行任务的线程,并返回一个包含未执行任务的列表。同样地,该方法也不会阻塞,会立即返回。
当调用了其中一个关闭方法后,可以通过isShutdown()方法判断线程池是否已经关闭,它会返回一个布尔值,表示线程池是否处于关闭状态。只有在所有的任务都执行完毕并且线程池处于关闭状态时,调用isTerminated()方法会返回true,表示线程池已经完全终止。
需要注意的是,在调用这两个关闭方法后,若还有任务在等待执行或者线程池中的任务无法正常终止,可以使用其他手段来处理,比如通过awaitTermination()方法等待一段时间,若超过指定时间还未终止,则可以选择强制退出。
关闭线程池是合理释放资源和保证系统稳定的重要操作,因此在使用完线程池后,一定要记得关闭线程池。
线程池的工作原理可以总结为以下几个步骤:
1、线程池判断核心线程池中的线程是否都在执行任务。如果还有空闲的线程,则从核心线程池中选取一个线程来执行新提交的任务。如果线程池中的线程都在执行任务,进入下一步。
2、线程池判断阻塞队列是否已满。如果阻塞队列未满,将新提交的任务放入阻塞队列中等待执行。阻塞队列起到了任务缓冲的作用,当线程池中的线程都在执行任务时,新提交的任务会被暂存到阻塞队列中,等待线程池中的线程空闲下来再执行。
3、如果阻塞队列已满,意味着线程池无法容纳更多的任务。这时,线程池会根据事先设置的饱和策略来处理新提交的任务。常见的饱和策略有:
以上是线程池的基本工作原理。线程池可以提高并发任务的执行效率和线程资源的利用率,通过合理设置线程池的大小和饱和策略,可以更好地适应系统的负载情况,提高系统的稳定性和性能。同时,线程池还提供了监控、统计以及任务取消和异常处理等机制,对于大规模并发任务的处理非常有用。
阻塞队列在线程池中的作用是用来存储等待执行的任务。它可以根据任务的先后顺序进行排序,通常按照先入先出的原则或者根据任务的优先级进行排序。
常见的阻塞队列有:
需要注意的是,SynchronousQueue中每个插入操作都必须等待一个对应的移除操作,否则插入操作将一直处于阻塞状态。
通过选择不同类型的阻塞队列,可以根据具体的需求来平衡吞吐量、公平性和任务执行顺序。常见的线程池实现类如Executors.newFixedThreadPool()和Executors.newCachedThreadPool()会使用不同的队列实现,根据具体的场景进行选择。
线程池的饱和策略是在队列和线程池都满载的情况下,决定如何处理新提交的任务。常见的饱和策略包括AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy和DiscardPolicy。
除了以上常见的饱和策略,我们还可以自定义饱和策略。通过实现RejectedExecutionHandler接口并重写rejectedExecution方法,我们可以自定义任务被拒绝时的处理逻辑。例如,可以记录日志、将任务存储到持久化队列中等。
需要根据具体的业务需求和系统特点选择合适的饱和策略。不同的策略可能会对系统的性能、稳定性和可靠性产生不同的影响。
总结:线程池提供了灵活、高效地管理和调度多线程任务的机制,能够提升系统性能、减少资源消耗。通过合理设置线程池的参数、选择合适的阻塞队列和饱和策略,可以根据实际需求来配置线程池,提高任务处理的效率和可靠性。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class main {
public static void main(String[] args) {
// 核心线程数
int corePoolSize = 5;
// 最大线程数
int maximumPoolSize = 10;
// 空闲线程存活时间
long keepAliveTime = 5000; // 5秒
// 阻塞队列
BlockingQueue workQueue = new ArrayBlockingQueue<>(100);
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.MILLISECONDS,
workQueue
);
// 提交任务给线程池执行
for (int i = 0; i < 20; i++) {
threadPool.execute(new Task(i));
}
// 关闭线程池
threadPool.shutdown();
}
static class Task implements Runnable {
private int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("执行任务:" + taskId);
}
}
}
在这个示例中,我们创建了一个ThreadPoolExecutor对象来管理线程池。通过设置核心线程数、最大线程数、空闲线程存活时间和阻塞队列等参数,来配置线程池的行为。
然后,我们使用execute方法提交任务给线程池执行。这里创建了20个任务,每个任务都是一个实现了Runnable接口的Task对象。
最后,通过调用shutdown方法来关闭线程池,等待所有任务执行完毕。注意,shutdown方法只是停止接收新任务,并且会等待已提交的任务执行完成。
以上就是一个使用ThreadPoolExecutor的简单示例,通过合理配置线程池参数,可以更好地控制线程池的行为和性能。
合理配置线程池参数是确保线程池能够高效运行的重要步骤。
总之,合理配置线程池参数需要综合考虑任务类型、系统负载、资源限制和业务需求等因素。通过不断试验和调整,找到最优的配置参数,以提高系统的性能和稳定性。