Thales的博客站
切换导航
导航
博客首页
关于博主
CV
工具
博客信息
快速了解java线程池
标签
:
java
线程池
作者:Thales 尊重博主原创文章,转载请注明文章出于此处。
>本文同时载于本人[CSDN](https://blog.csdn.net/Tales_/article/details/82696284) ## 1.概述 使用Thread类执行某个任务,任务运行时每次都要创建线程,任务结束则要销毁线程,对系统而言,线程不仅是资源,线程的创建与销毁也要消耗系统的资源。对于此问题,很显然一个直接的解决方案就是复用这个线程,让线程执行完某个任务后仍然能继续执行执行其它任务,而不是销毁。线程池就提供了这样的解决方案。 线程池指的是存在一组线程,这组线程创建后一般不会销毁,而是进入休眠状态,等到有新的任务需要执行又重新恢执行。 ## 2.创建一个线程池 java.util.concurrent(JUC)包提供了很多接口用于线程池的创建。 ### 2.1 ExecutorService 接口 线程池通常指的是`ExecutorService` —— 一个提供了终止管理的方法和可以生成Future的方法的`Executor`。简单地说,`ExecutorService`接口继承了`Executor`接口,`ExecutorService`除了存在`Executor`提供的execute方法外,还提供了很多终止管理的方法以及产生用于跟踪线程执行结果的Future的submit。如下所示 ```java boolean awaitTermination(long timeout, TimeUnit unit)
List
> invokeAll(Collection extends Callable
> tasks)
List
> invokeAll(Collection extends Callable
> tasks, long timeout, TimeUnit unit)
T invokeAny(Collection extends Callable
> tasks)
T invokeAny(Collection extends Callable
> tasks, long timeout, TimeUnit unit) boolean isShutdown() boolean isTerminated() void shutdown() List
shutdownNow()
Future
submit(Callable
task) Future> submit(Runnable task)
Future
submit(Runnable task, T result) ``` ### 2.2 直接通过 ThreadPoolExecutor创建线程池 JUC提供了ThreadPoolExecutor类用于创建线程池,这个类继承了实现ExecutorService接口的AbstractThreadPoolExecutor。ThreadPoolExecutor有若干个构造参数,一般可以通过调用如下所示的创建一个线程池: ```java ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue) ``` 代码如下 ```java ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new BlockingArrayQueue(40)); ``` 其中,各个参数的含义如下: - `corePoolSize` 线程池中保持存活的线程数量,即使线程是空闲的,除非调用allowCoreThreadTimeOut(true) - `corePoolSize` 线程池中最大能创建的线程数量 - `keepAliveTime` 空闲线程的存活时间 - `unit` KeepAliveTime参数的单位例如 TimeUnit.SECONDES等 - `workQueue` 一个储存所提交的runnable的任务的队列,只会execute方法提交的runnable任务 通过上面的参数描述,我们可以知道,通过ThreadPoolExecutor创建的的线程池是可以自动调整大小的。当一个新的任务提交时,如果此时线程池中的线程小于`corePoolSize`,那么线程池就会创建一个新的线程来执行此任务,即使其它的线程处于空闲状态。如果此时线程池中存在大于`corePoolSize`但是小于`maximumPoolSize`的线程数,除非`workQueue`是满的,否则不会创建新的线程。因此,我们可以通过设置`corePoolSize`和`maximumPoolSize`相等来创建一个固定线程数的线程池,也可以通过设置一个实质上没有边界的的值例如`Integer.MAX_VALUE`来创建可以容纳任意并发线程数的线程池。一般来说,`corePoolSize`和`maximumPoolSize`在线程池构造时就已经确定,但是我们也可以通过 `setCorePoolSize(int)` 和 `setMaximumPoolSize(int)`动态设置这两个值。 ### 2.3 线程池中线程的创建 线程池使用ThreadFactory创建新线程。如果没有另外指定,则使用`Executors.defaultThreadFactory()`,它将所有线程创建在同一个ThreadGroup中,并具有相同的`NORM_PRIORITY`优先级和非守护进程状态。 通过提供不同的ThreadFactory,我们可以更改线程的名称,线程组,优先级,守护程序状态等。如果`ThreadFactory`调用newThread时返回`null`创建线程失败,则executor将继续,但可能无法执行任何任务。 线程应该拥有“modifyThread”的RuntimePermission。如果使用线程池的工作线程或其他线程不具有此权限,则服务可能会降级:配置更改可能不会及时生效,并且关闭线程池可能保持可以终止但未完成的状态。 ### 2.4 线程存活时间 如果池当前具有多于`corePoolSize`的线程,则多余线程如果空闲时间超过`keepAliveTime`,则将终止(请参阅`getKeepAliveTime(TimeUnit)`)。 这提供了一种在不使用池时减少资源消耗的方法。如果线程池稍后变得更活跃,则将构造新线程。 也可以使用方法setKeepAliveTime(long,TimeUnit)动态更改此参数。使用`Long.MAX_VALUE` `TimeUnit.NANOSECONDS`这样的值将有效地禁止空闲线程在线程池关闭之前终止。 默认情况下,仅当存在大于corePoolSize的线程时,保持活动策略才适用。但是方法`allowCoreThreadTimeOut(boolean)`也可用于将此超时策略应用于核心线程,只要keepAliveTime值不为零。 ###2.5 工作队列 任何BlockingQueue都可用于传输和保存提交的任务。此队列的使用与池大小相互影响: - 如果运行的线程少于corePoolSize,则Executor总是更喜欢添加新线程而不是将任务加入队列。 - 如果corePoolSize或更多线程正在运行,则Executor总是更喜欢排队请求而不是添加新线程。 - 如果队列已经满了,则会创建一个新线程,除非它超过maximumPoolSize,在这种情况下,该任务将被拒绝。 队列选择有三种常规策略: - 直接交接。工作队列的一个很好的默认选择是`SynchronousQueue`,它将任务交给线程而不另外保存它们。如果没有线程立即可用于执行任务,则尝试添加一个新的任务将会失败,因此将构造新线程。此策略在处理可能具有内部依赖性的请求集时避免了锁定。直接交接法通常需要无边界的`maximumPoolSizes`以避免拒绝新提交的任务。这也就意味着,当任务以比处理它们更快的速度到达时,线程可能会无限制增长。 - 无限队列。使用无限制队列(例如,没有预定义容量的LinkedBlockingQueue)将导致新任务在没有空闲线程时在队列中等待。因此,线程池只会创建corePoolSize数量的线程(`maximumPoolSize`的值因此没有任何效果)。当每个任务完全独立于其他任务时,这可能是适当的,因此任务不会影响彼此的执行;例如,在网页服务器中。虽然这种排队方式可以有助于平滑瞬态突发请求,但是这同时意味着当任务继续平均到达的速度超过可处理速度时,工作队列可能会无限制增长。 - 有限的队列。有限队列(例如,ArrayBlockingQueue)与有限maximumPoolSizes一起使用时有助于防止资源耗尽,但可能更难以调整和控制。队列大小和最大池大小之间可以相互折中(trade off):使用大型队列和小型池可以最小化CPU使用率,OS资源和上下文切换开销,但可能导致人为的低吞吐量。如果任务经常阻塞(例如,如果它们执行时存在I/O操作),则系统可能能够提供比`maximumPoolSizes`更多的线程调度时间。使用小队列通常需要更大的池大小,这会使CPU更加繁忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量。 ### 2.6 任务提交 提交任务有两种方法,一种是提交无返回值的任务,使用如下方式: ```java ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new BlockingArrayQueue(40)); Runnable handler = ......; executor.execute(handler); ``` 如果需要获得任务的执行结果,则可以通过调用submit方法来提交任务,代码如下: ```java ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new BlockingArrayQueue(40)); Callable handler = ......; Future future = executor.submit(handler); ``` ### 2.7 创建线程池的完整实例 ```java import java.util.concurrent.*; public TreadPoolTest(){ public static void main(String[] args){ ExecutorService executor = new TreadPoolExecutor(5, 10, 10L,TimeUnit.SECONDS,new ArrayBlockingQueue(40)); for(int i = 0;i < 10; i++){ Handler handler = new Handler(); executor.execute(handler); } executor.shutdown(); } class Handler extends Runnable{ public void run(){ for(int i = 0; i < 5;i++){ System.out.println(i); } } } } ``` ## 3 通过Executor的工厂方法创建线程池 Executors是JUC包下的一个工厂类,他提供了许多便捷的方式创建线程池。 ### 3.1 创建固定大小的线程池 可以通过Executors类创建一个固定大小的线程池: ```java ExecutorService executor = Executors.newFixedThreadPool(10); ``` 我们前面已经说过,固定大小的线程池可以通过设置ThreadPoolExecutor 核心线程数和最大线程数相等实现,事实上源码也是如此的: ```java public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
()); } ``` 一般来说,当线程数达到核心线程数,既不会再创建线程,也不会销毁线程,除非发生异常某个线程终止,或者设置allowCoreTimeOut,但最好别这么做,因为keepAlive参数此时被设置为0,可能会导致频繁的创建与销毁线程(这里仅仅是推测。仍需验证)。 ### 3.2 创建可缓存的线程池: 可以通过`Executors.newCachedThreadPool` 创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模无限制。代码如下: ```java ExecutorService executor = Executors.newCachedThreadPool(10); ``` 前面同样也提到,可以设置`maxmumPoolSize`参数为一个实质上无边界的值如`Integer.MAX_VALUE`让线程池的规模无限制,实际上源码也是如此实现的: ```java public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, SynchronousQueue
()); } ``` ### 3.3 创建单线程的线程池 可以通过`Executors.newSingleThreadExecutor()` 创建一个单线程的Executor,确保任务串行执行: ```java ExecutorService executor = Executors.newSingleExecutor(); ``` ```java public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
())); } ``` ### 4 总结 这篇文章深入描述线程池的创建方法,各个参数的意义与作用,但是线程池除以上所描述的,还存在可调度的线程池,在本系列的下一篇文章将讲述这一类线程池。
发布于:『2018-09-13 14:51』
博客类别:
java
阅读(2664) 评论(0)
上一篇:
Java NIO Selector简介
下一篇:
Java后端知识点整理
用户评论
暂无评论
发表评论
验证码:
发表评论
文章分类
java(10)
Spring(0)
其它(1)
文章存档
2018年10月(1)
2018年09月(8)
2018年07月(2)
友情链接
分享到
暂无评论