JUC并发编程_线程池
- 一、线程池的介绍
- 二、线程池的创建方式
- 1、通过 Executors 工厂类创建
- 2、通过ThreadPoolExecutor构造函数创建
- 三、线程池的工作流程
- 四、线程池的四种拒绝策略
- 五、最大线程数如何设置
- 六、注意事项
一、线程池的介绍
线程池的组成:
Java中的线程池主要由 java.util.concurrent 包下的Executor、ExecutorService、ThreadPoolExecutor等接口和类提供支持。其中,ThreadPoolExecutor是线程池的核心实现类。
Executor
:线程池的顶级接口,定义了execute(Runnable command)方法,用于提交任务。
ExecutorService
:Executor的子接口,增加了管理线程池生命周期和任务执行的方法,如submit(Callable/Void)、shutdown()、shutdownNow()等。
ThreadPoolExecutor
:ExecutorService 的默认实现,提供了丰富的配置选项,如核心线程数、最大线程数、工作队列类型、线程存活时间等。
线程池的好处:
1、降低资源消耗(线程复用)
2、提高响应速度
3、方便管理(控制最大并发数)
二、线程池的创建方式
1、通过 Executors 工厂类创建
不推荐使用 Executors 创建线程池,它们的阻塞队列长度都是 Integer.MAX_VALUE
,可能会堆积大量请求,从而导致 OOM
newFixedThreadPool(int nThreads)
:创建一个固定大小的线程池,适用于控制最大并发数的场景
//实例化一个固定大小为10个线程的newFixedThreadPool线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);try {for (int i = 0; i < 10; i++) {threadPool.execute(() -> {System.out.println(Thread.currentThread().getName() + " ok");});}
} catch (Exception e) {e.printStackTrace();
} finally {//关闭线程池threadPool.shutdown();
}
newSingleThreadExecutor()
:创建一个单线程的线程池,保证所有任务按照指定顺序执行。
newCachedThreadPool()
:创建一个可缓存的线程池,适用于执行大量短期异步任务的场景。
newScheduledThreadPool(int corePoolSize)
:创建一个定时线程池,用于定时及周期性任务执行。
2、通过ThreadPoolExecutor构造函数创建
使用 ThreadPoolExecutor
的构造函数可以更加灵活地配置线程池,如指定核心线程数、最大线程数、线程存活时间、工作队列等参数。
public ThreadPoolExecutor(int corePoolSize, //核心线程数int maximumPoolSize, //最大线程数long keepAliveTime, // 非核心线程最大空闲时间TimeUnit unit, // 最大空闲时间单位BlockingQueue<Runnable> workQueue, //阻塞队列ThreadFactory threadFactory, //线程工厂RejectedExecutionHandler handler //拒绝策略) {
}
// 创建一个容量为10的阻塞队列
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);// 创建ThreadPoolExecutor(核心线程数为10,最大线程数为15,非核心线程最大空闲时间为30s,阻塞队列容量为10,默认线程工厂,默认拒绝策略)
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 30, TimeUnit.SECONDS,workQueue);
三、线程池的工作流程
当有新任务提交时,线程池会检查是否有空闲的线程。
如果有空闲线程,则直接将任务分配给该线程执行。
如果没有空闲线程,则检查当前线程数是否达到了核心线程数。
如果未达到核心线程数,则创建新的线程来执行任务。
如果已达到核心线程数,则将任务加入到任务队列中等待执行。
如果任务队列已满,且当前线程数小于最大线程数,则创建新的线程来处理任务。
如果当前线程数等于最大线程数,且任务队列已满,则根据拒绝策略处理无法执行的任务。
四、线程池的四种拒绝策略
当任务队列已满,且无法创建新的线程来执行任务时,线程池会根据配置的拒绝策略来处理这些任务。常见的拒绝策略包括:
AbortPolicy
:抛出 RejectedExecutionException 异常,默认策略。
CallerRunsPolicy
:由调用线程直接执行任务。
DiscardPolicy
:丢弃任务,不抛出异常。
DiscardOldestPolicy
:丢弃队列中等待最久的任务,然后执行当前任务。
// 创建一个容量为10的阻塞队列
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
// 创建ThreadPoolExecutor,并指定 DiscardOldestPolicy 作为拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 30,TimeUnit.SECONDS,workQueue,// 使用默认的线程工厂(这里不显式传递ThreadFactory)new ThreadPoolExecutor.DiscardOldestPolicy() // 指定DiscardOldestPolicy作为拒绝策略
);
五、最大线程数如何设置
//获取本机的逻辑处理器数量
int availableProcessors = Runtime.getRuntime().availableProcessors();
针对CPU密集型的任务,一般将线程池的最大线程数设置的少一点
针对IO密集型的任务,一般将最大线程数设置的多一点
六、注意事项
- 合理配置线程池的参数,如核心线程数、最大线程数、线程存活时间等,以适应不同的应用场景。
- 使用有界队列,防止任务队列过长导致内存溢出。
- 根据具体应用场景选择合适的拒绝策略。
- 使用完线程池后,应调用shutdown()或shutdownNow()方法来释放资源。