网站开发公司怎么找客户,邳州做网站的公司,广州网站建设说说外链的建设,wordpress 侧边栏短代码序言
在高并发编程中#xff0c;线程池是一个非常重要的组件。它不仅能够有效地管理和复用线程资源#xff0c;还可以提升应用程序的性能和稳定性。本文将详细介绍Java中的线程池机制#xff0c;以及如何正确地使用线程池。
一、什么是线程池
线程池是一组已经初始化并等…序言
在高并发编程中线程池是一个非常重要的组件。它不仅能够有效地管理和复用线程资源还可以提升应用程序的性能和稳定性。本文将详细介绍Java中的线程池机制以及如何正确地使用线程池。
一、什么是线程池
线程池是一组已经初始化并等待执行任务的线程集合。通过使用线程池我们可以避免频繁地创建和销毁线程从而节省资源和减少系统开销。线程池的核心思想是通过复用线程来提高性能。
二、为什么使用线程池
资源消耗通过池化技术重复利用已创建的线程降低线程创建和销毁造成的损耗。提高响应速度任务到达时无需等待线程创建即可立即执行。提高线程的可管理性线程是稀缺资源如果无限制创建不仅会消耗系统资源还会因为线程的不合理分布导致资源调度失衡降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
三、Java 中的线程池
Java 提供了 java.util.concurrent 包来支持并发编程。其中有几种方法创建线程池
1、通过 Executors 工厂类的静态方法创建线程池
newFixedThreadPool /*** 创建一个可重用固定数量线程的线程池这些线程操作于共享的无界队列。* 在任何时候最多有 {code nThreads} 个线程会处于活动状态处理任务。* 当所有线程都在活动时如果提交了额外的任务它们将在队列中等待* 直到有线程可用。* 如果在关闭前任何线程因执行失败而终止将根据需要创建一个新的线程来执行后续任务。* 线程池中的线程将持续存在直到显式调用 {link ExecutorService#shutdown shutdown} 方法关闭它。** param nThreads 线程池中的线程数量* return 新创建的线程池* throws IllegalArgumentException 如果 {code nThreads 0}*/public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable());}newSingleThreadExecutor /*** 创建一个使用单一工作线程的ExecutorService来执行任务。* 此执行器服务使用单一线程以顺序方式处理任务确保每次只执行一个任务。* 即使在执行前一个任务时因失败导致线程终止在关闭前也会创建新的线程继续执行后续任务。* 与等效的{code newFixedThreadPool(1)}不同返回的执行器保证不能重新配置为使用额外的线程。** return 新创建的单线程ExecutorService*/
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable()));
}其中这两个线程池里面使用到的阻塞队列如下
// 队列的最大长度是Integer的最大整数
public LinkedBlockingQueue() {this(Integer.MAX_VALUE);
}newCachedThreadPool
/*** 创建一个可根据需要创建新线程的线程池同时会重用之前已构建的空闲线程。* 这种线程池通常能提升执行大量短生命周期异步任务程序的性能。* 调用{code execute}方法时如果存在可用的先前构建的线程则会重用它们。* 若无现有线程可用将创建新线程并加入到线程池中。* 在线程闲置六十秒后线程将被终止并从缓存中移除。* 因此长时间处于空闲状态的线程池不会消耗任何资源。* 注意可以通过{link ThreadPoolExecutor}构造器创建具有类似特性但细节不同的例如超时参数线程池。** return 新创建的线程池实例*/
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueueRunnable());
}注一般在工程上不建议使用它们创建线程池阿里java开发手册 2、通过 ScheduledExecutorService 创建定时任务线程池
ScheduledExecutorService 是 ExecutorService 的子接口专门用于支持定时及周期性任务执行的线程池。可以通过 Executors.newScheduledThreadPool(int corePoolSize) 方法创建 /*** 创建一个可以调度命令在指定延迟后运行或周期性执行的线程池。* * param corePoolSize 即使在空闲时线程池中也要保持的线程数量* return 新创建的ScheduledExecutorService实例即计划线程池* throws IllegalArgumentException 如果corePoolSize小于0*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,new DelayedWorkQueue());
}3、通过 ThreadPoolExecutor 构造函数直接创建
使用 ThreadPoolExecutor 的构造函数可以更加灵活地配置线程池例如指定核心线程数、最大线程数、线程存活时间、工作队列等参数如下所示 public ThreadPoolExecutor(int corePoolSize, // 核心线程数int maximumPoolSize, // 最大线程数long keepAliveTime, // 线程存活时间TimeUnit unit, // 线程存活时间单位BlockingQueueRunnable workQueue, // 阻塞队列ThreadFactory threadFactory, // 线程工厂RejectedExecutionHandler handler //拒绝策略
)四、ThreadPoolExecutor 线程池的执行过程
流程图是这样的 整体流程就是
1、任务来的时候如果当前线程数小于核心线程数则创建线程执行任务
2、否则将任务添加到阻塞队列
3、如果队列已满case1如果当前线程数小于最大线程数则创建线程执行任务case2如果当前线程数大于等于最大线程数则执行拒绝策略其实这个流程整体上来说是没什么问题的也是大多数面试者回答该问题的标准答案。 但是这里有一个小问题就是当我们把核心线程数设置为0的时候声明的阻塞队列长度随便给一个大于0的值即可(此处我使用LinkedBlockingQueue队列它的默认长度是Integer的最大值)。 我们来看看下面的代码大家觉得会输出什么
public static void testThreadPool() {ThreadPoolExecutor threadPoolExecutor new ThreadPoolExecutor(0, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());threadPoolExecutor.execute(()-{System.out.println(线程池测试: new Date());});
}如果按照上面流程图的步骤进行的话什么也不会输出因为此任务会加入到阻塞队列里面对不对 其实它输出了结果如下 你现在可能有一个大大问号其实我们可以从源码那里得到答案源码如下
public void execute(Runnable command) {// 检查传入任务是否为空为空抛出异常if (command null)throw new NullPointerException();// 获取当前线程池控制状态int c ctl.get();// 1、如果当前线程数小于核心线程数if (workerCountOf(c) corePoolSize) {// 添加一个新的工作线程来执行任务// 添加成功直接返回if (addWorker(command, true))return;// 再次获取线程池控制状态防止并发c ctl.get();}// 2、如果线程池处于运行状态并且任务能够加入队列if (isRunning(c) workQueue.offer(command)) {// 再次获取线程池控制状态防止并发int recheck ctl.get();// 2.1、如果线程池不再运行并且任务能够从队列中移除则拒绝任务if (! isRunning(recheck) remove(command))reject(command);// 2.2、如果当前线程池没有运行的线程(此处可以打消你的问号)else if (workerCountOf(recheck) 0)// 添加一个新的非核心工作线程addWorker(null, false);}// 3、添加一个新的非核心工作线程如果失败拒绝任务else if (!addWorker(command, false))reject(command);
}所以说从源码的角度具体去分析线程池执行任务的流程如下
1、任务来的时候如果当前线程数小于核心线程数则创建线程执行任务
2、否则线程池处于运行状态并且任务能够加入队列case1判断线程池运行状态如果线程池状态不是运行的情况下同时任务可以从队列移除那么就拒绝该任务case2判断当前是否有工作线程如果没有则创建一个非核心的工作线程
3、如果队列已满case1如果当前线程数小于最大线程数则创建线程执行任务case2如果当前线程数大于等于最大线程数则执行拒绝策略五、线程池的拒绝策略
名称描述AbortPolicy丢弃任务并抛出RejectedExecutionException异常。这是线程池默认的拒绝策略在任务不能再提交的时候抛出异常及时反馈程序运行状态。如果是比较关键的业务推荐使用此拒绝策略这样子在系统不能承载更大的并发量的时候能够及时的通过异常发现。CallerRunsPolicy由调用线程(提交任务的线程)处理该任务。这种情况是需要让所有任务都执行完毕那么就适合大量计算的任务类型去执行多线程仅仅是增大吞吐量的手段最终必须要让每个任务都执行完毕。DiscardPolicy丢弃任务但是不抛出异常。 使用此策略可能会使我们无法发现系统的异常状态。建议是一些无关紧要的业务采用此策略。DiscardOldestPolicy丢弃队列最前面的任务然后重新提交被拒绝的任务。是否要采用此种拒绝策略还得根据实际业务是否允许丢弃老任务来认真衡量。
六、线程池提交任务
线程池有两种提交任务的方式
1、使用submit(Runnable task),有返回值
public static void testThreadPool() {ThreadPoolExecutor threadPoolExecutor new ThreadPoolExecutor(0, 10, 60,TimeUnit.SECONDS,new LinkedBlockingQueue(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());Future? submit threadPoolExecutor.submit(() - {System.out.println(线程池测试submit: new Date());return 1;});try {Object o submit.get();System.out.println(o);} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}
}2、使用execute(Runnable task)没有返回值
public static void testThreadPool() {ThreadPoolExecutor threadPoolExecutor new ThreadPoolExecutor(0, 10, 60,TimeUnit.SECONDS,new LinkedBlockingQueue(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());threadPoolExecutor.execute(()-{System.out.println(线程池测试execute: new Date());});
}七、线程池的状态
名称描述RUNNING会接收新任务并且会处理队列中的任务SHUTDOWN不会接收新任务并且会处理队列中的任务任务处理完后中断所有线程STOP不会接收新任务并且不会处理队列中的任务直接中断所有线程TIDYING所有任务都已终止workerCount为0TERMINATEDterminated()执行完成之后就会转变为TERMINATED
这五种状态之间的转换
RUNNING - SHUTDOWN调用shutdown方法RUNNING or SHUTDOWN - STOP调用shutdownNow方法SHUTDOWN - TIDYING阻塞队列为空线程池中工作线程数为0STOP- TIDYING线程池中工作线程数为0TIDYING - TERMINATED执行erminated方法