java.util.concurrent.ThreadPoolExecutor类来创建完成基础pool
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handlaer)
下面对参数进行说明
1:corePoolSize:表示核心线程池的大小.当提交一个任务时,如果当前核心线程池的线程个数没有达到corePoolSize,则会创建新的线程来执行所提交的任务,即使当前核心线程池有空闲的线程。如果当前线程核心池的线程个数达到了corePoolSize,则不再重新创建线程.如果调用了prestartCoreThread()或者 prestartAllCoreThreads(),线程池创建的时候所有的核心线程都会被创建并且启动。
2:maximumPoolSize:表示当前线程池能创建线程的最大个数.如果当阻塞队列已满时,并且当前线程个数没有超过maximumPoolSize的话,就会创建新的线程来执行任务。
3:keepAliveTime:空闲线程存活时间。如果当前线程池的线程个数已经超过了corePoolSize,并且线程空闲时间超过了keepAliveTime的话,就会将这些空闲线程销毁,这样可以尽可能降低系统资源消耗。
4:unit:时间单位.为keepAliveTime指定时间单位。
5:workQueue:阻塞队列.用于保存任务的阻塞队列,可以使用 ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue,DelayedWorkQueue根据不同的队列类,赋予该线程池不同的功能。后续会详细介绍
6:threadFactory:创建线程的工程类.可根据该线程池实际作用业务需求)创建自己特有的名称,这样的话当出现bug时可以即使定位问题,找到原因所在
7:handler:饱和策略.当线程阻塞队列中任务已满并且已达到最大线程数,那么说明该当前线程池已经处于饱和状态了,无法再创建新的线程来接收任务来,那么此时就需要采用一种策略来处理这种情况。目前通用采用的策略有以下这几种:
AbortPolicy: 直接拒绝提交的任务,并抛出RejectedExecutionException异常;
CallerRunsPolicy:只用调用者所在的线程来执行任务;
DiscardPolicy:不处理直接丢弃掉任务;
DiscardOldestPolicy:丢弃掉阻塞队列中存放时间最久的任务,执行当前任务
java.util.concurrent.Executors提供了4种不同类型线程池来供我们使用。本质是创建不同的workQueue来提供不同功能
1:Executors.newFixedThreadPool(int nThreads)
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);Executors提供了两种不同构建方法,不同在于是否自定义指定ThreadFactory。从上述源码可知道newFixedThreadPool的
workQueue使用是LinkedBlockingQueue(底层基于链表保存数据),指定capacity(容量)=Integer.MAX_VALUE,意味该队列是一个无界队列;核心线程数与最大线程数相同;这样的设置使得该线程池的实际线程数达到核心线程数后的任务都会保存到任务队列中等待执行,理论上是不会出现触发饱和策略的情况。使用该线程池时注意如果任务过大,或任务执行时间过长导致队列中挤压任务过大会导致内存溢出。
2:Executors. 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); }从上述源码可以看出,newCachedThreadPool的两种构建方法的差距为是否需要自定义ThreadFactory。指定核心线程数为0,最大线程数为int的最大值,workQueue使用SynchronousQueue(简单理解SynchronousQueue是一个没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,必须等队列中的添加元素被消费后才能继续添加新的元素),keepAliveTime=60s.这样的设置在多任务情况下,每提交一个任务pool就会启用一个线程来处理任务(如果当前pool中无空闲线程可用的话).当任务结束,pool会清理空闲时间超过60s的线程。优点:在少任务的情况下,可以起到一定缓存线程的作用(减少创建不必要的线程,最大化利用空闲线程)一但任务过多,该pool会创建与任务数相对应的线程数
3:Executors.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)); }从方法名称上也可以知道该线程池与单例有关,newSingleThreadExecutor的两种构建方法的差距为是否需要自定义ThreadFactory,指定核心线程数与最大线程为1(代表整个线程池中的线程数永远为1个即Single),workQueue使用是LinkedBlockingQueue(底层基于链表保存数据),指定capacity(容量)=Integer.MAX_VALUE,意味该队列是一个无界队列。整个线程池中只有一个线程执行阻塞队列中的所有任务。
4:Executors.newScheduledThreadPool(int corePoolSize)
//ScheduledThreadPoolExecutor继承了ThreadPoolExecutor 通过查看源码发现 //本质仍然是调用是使用以下的ThreadPoolExecutor的构造函数 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.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; } public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1)); } public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1, threadFactory)); }创建一个可以用来给定延时后执行异步任务或周期性执行任务的线程池。workQueue使用是DelayedWorkQueue。DelayedWorkQueue 阻塞队列:保证执行任以及能够周期执行任务,在执行定时任务的时候,每个任务执行时间不同,所以DelayedWorkQueue的工作就是按照执行时间的升序来排序,执行时间距离当前时间越近的任务在最前面
当上述java提供的4种线程池不能满足与我们实际应用,我们需要根据业务不同,通过设置不同参数构建不同的线程池来实现不同业务场景。
如何合理的配置线程池参数(可根据使用线程池的用途来分析):
想要合理的配置线程池,就必须首先分析任务特性,我们可以从以下几个角度来进行分析:
1:任务的性质:任务 属于哪种类型,是属于CPU密集型任务 ,还是属于IO密集型任务,还是属于混合型任务
2:任务的优先级:任务的执行是否会依赖某种属性定义为高,中,低。例如时间,或任务的重要程度
3:任务的执行时间:长,中,短
4:任务的依赖性:是否依赖其他外部资源,如访问外部api接口。
不同的性质的任务可以放到不同规模的线程池分开处理。CPU密集型任务配置尽可能少的线程数量,如配置Ncpu+1个线程的线程池。IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型和一个IO型任务,只要这两个执行时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解.通过Runtime.getRuntime().availableProcessors()方法我们可以获得当前设备的CPU个数。
优先级不同的任务可以使用优先级队列PriorityBlockingQueue(可排序阻塞队列)来处理。它可以让优先级高的任务先得到执行,如果一直有优先级高的任务被提交到队列里,那么优先级低的任务可能永远不能执行( 饥饿线程)。
依赖访问外部api接口任务,因为线程发起请求后需要等待api响应,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。(针对这种情况请求一定要设置超时时间,超时时间值的大小最好根据实测接口访问平均时间来定)
建议阻塞队列最好是使用有界队列,如果采用无界队列的话,一旦任务积压在阻塞队列中的话就会占用过多的内存资源,甚至会使得系统崩溃。
本文详细介绍了Java线程池ThreadPoolExecutor的参数,包括corePoolSize、maximumPoolSize、keepAliveTime、workQueue等,并讨论了四种Executors提供的线程池:newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor和newScheduledThreadPool。合理配置线程池参数需要考虑任务性质、执行时间和依赖性等因素。
170万+

被折叠的 条评论
为什么被折叠?



