线程池
使用线程池的目的
-
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-
提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
-
线程池参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
int corePoolSize: 核心线程数最大值
int maximumPoolSize:该线程池中线程总数最大值
long keepAliveTime:非核心线程闲置超时时长
TimeUnit unit:时间单位(枚举:纳秒,微秒,毫秒,秒,分,时,天)
BlockingQueue workQueue: 当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务
ThreadFactory threadFactory: 创建线程的方式
RejectedExecutionHandler handler:任务队列和最大线程都满了之后的拒绝策略
线程池原理
-
提交一个任务,如果线程池存活线程数小于核心线程数corePoolSize,直接创建线程处理新来的任务。
-
如果核心线程已满,新来的任务放入任务队列workQueue等待执行。
-
核心线程与任务队列都满了以后,判断当前线程数是否达到最大线程数maximumPoolSize,没达到就创建非核心线程执行任务。
-
当前线程数达到了最大线程数,采用拒绝策略handler处理。
阻塞队列
ArrayBlockingQueue: 基于数组的阻塞队列实现。生产者放入数据和消费者获取数据,共用同一个锁对象。默认采用非公平锁。
LinkedBlockingQueue:基于链表的阻塞队列。生产者端和消费者端分别采用了独立的锁来控制数据同步,并发性能较好。需要注意的是,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE)。
PriorityBlockingQueue:基于优先级的阻塞无界队列(优先级的判断通过构造函数传入的Compator对象来决定,但不保证同优先级元素顺序)。不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。内部控制线程同步的锁采用的是公平锁。
SynchronousQueue: 无缓冲的等待队列。每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都要等待另一个线程的插入操作。由于队列没有容量,所以不能调用peek操作(返回队列头元素)。
DelayQueue: 支持延时获取元素的无界阻塞队列。队列中每个元素必须实现Delayed接口。插入数据的操作(生产者)永远不会被阻塞,只有获取数据的操作(消费者)才会被阻塞。
拒绝策略
AbortPolicy:java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute需要try catch,否则程序会直接退出。
DiscardPolicy:直接抛弃,任务不执行,空方法。
DiscardOldestPolicy:从队列里面抛弃head的一个任务,并再次execute 此任务。
CallerRunsPolicy:在调用execute的线程里面执行此任务,会阻塞入口。
线程池分类
newFixedThreadPool
固定数目线程的线程池
适用于处理CPU密集型任务,长期的任务。
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);
}
newSingleThreadExecutor
适用于需要保证顺序地执行各个任务 。 它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
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));
}
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);
}
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行
和其他线程池最大的区别是使用的阻塞队列是 DelayedWorkQueue,而且多了两个定时执行的方法scheduleAtFixedRate和scheduleWithFixedDelay
-
scheduleAtFixedRate() :按某种速率周期执行
-
scheduleWithFixedDelay():在某个延迟后执行
两种方法的内部实现都是创建了一个ScheduledFutureTask对象封装了任务的延迟执行时间及执行周期,并调用decorateTask()方法转成RunnableScheduledFuture对象,然后添加到延迟队列中。
DelayQueue:中封装了一个优先级队列,这个队列会对队列中的ScheduledFutureTask 进行排序,两个任务的执行 time 不同时,time 小的先执行;否则比较添加到队列中的ScheduledFutureTask的顺序号 sequenceNumber ,先提交的先执行。 具体执行步骤: 线程从 DelayQueue 中获取 time 大于等于当前时间的 ScheduledFutureTask DelayQueue.take() 执行完后修改这个 task 的 time 为下次被执行的时间 然后再把这个 task 放回队列中 DelayQueue.add()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(
corePoolSize,
Integer.MAX_VALUE,
0,
NANOSECONDS,
new DelayedWorkQueue());
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize,ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(
int corePoolSize,ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
newWorkStealingPool
适合使用在很耗时的操作,但是newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展,但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中 。
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool(
parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null,
true);
}
线程池状态
-
RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
-
SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);
-
STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;
-
TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
-
TERMINATED
:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
进入TERMINATED的条件如下:
-
线程池不是RUNNING状态;
-
线程池状态不是TIDYING状态或TERMINATED状态;
-
如果线程池状态是SHUTDOWN并且workerQueue为空;
-
workerCount为0;
-
设置TIDYING状态成功。
-
合理地配置线程池
要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。
任务的性质:CPU密集型任务、IO密集型任务和混合型任务
任务的优先级:高中低
任务的执行时间:长中短
任务的依赖性:是否依赖其他系统资源
-
CPU密集型任务应配置尽可能小的线程,如配置NCPU+1个线程的线程池。
-
IO密集型任务线程并不是一直在执行任务 ,则应配置尽可能多的线程,如2*NCPU 。
-
对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了。
可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
-
优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理,它可以让优先级高的任务先执行。
-
若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。
-
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池? (1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换。 (2)并发不高、任务执行时间长的业务要区分开看: a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务。 b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样,线程池中的线程数设置得少一些,减少线程上下文的切换。 (3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。
线程池的监控
通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用
-
getTaskCount:线程池已经执行的和未执行的任务总数;
-
getCompletedTaskCount:线程池已完成的任务数量,该值小于等于taskCount;
-
getLargestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过,也就是达到了maximumPoolSize;
-
getPoolSize:线程池当前的线程数量;
-
getActiveCount:当前线程池中正在执行任务的线程数量。
浙公网安备 33010602011771号