内容
在公司中处理kafka任务时,我运用了线程池来创建多个线程处理来处理kafka命令。当然创建的线程是固定线程池,当时的考虑是因为资源的有限性,并且当时是对kafka中传过来的视频和图片多编解码,所以占用时间又点长。为了合理利用线程池,专门去学习关于这方面的知识,分享给大家!
线程池介绍
1.为什么要使用线程池?
- 降低资源的消耗
- 提高响应的速度
- 提高线程的管理
2. 线程的工作原理
- 线程池判断核心线程池的线程是否都在执行任务。如果不是,创建一个新的工作线程来执行任务。如果核心线程里面的线程都在执行任务,进入下一个流程
- 线程池判断工作队列是否已满,如果未满,则将新的任务存储在这个工作队列里面,如果满了,进入下一个流程
- 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略处理这个任务
3. ThreadPoolExecutor的核心参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) //后两个参数为可选参数
- corePoolSize(核心线程数)
- runnableTaskQueue(任务队列),常见的任务队列:ArrayBlockingQueue,LinkedBlockingQueue(FixThreadPool和SingleThreadPool使用这个队列,使maximumPoolSize参数失效),SynchronousQueue(CacheThreadPool使用这个队列存储任务,其实这种队列不存储元素),PriorityBlockingQueue(ScheduleThreadPool使用的DelayQueue其实就是这种队列)
- maximumPoolSize(线程池最大数量)
- ThreadFactory(用于设置线程的工厂)
- RejectedExecutionHandler(饱和策略),主要有四个策略:AbortPolicy,直接抛出异常
CallerRunsPolicy,只用调用者所在线程运行任务,DiscardOldestPolicy,丢弃队列中最近的一个任务,DiscardPolicy,不处理,丢弃掉
4.CacheThreadPool,SingleThreadExecutor,FixedThreadPool已知线程池之间的比较:
这三种线程池有它们固定的应用的场景
1.FixedThreadPool,适用于为了满足资源管理的需求,需要限制当前线程数量的应用场景,适用于负载比较重的服务器
2.SingleThreadExecutor,适用于保证顺序指定各个人物,并且在任意时间点,不会有多个线程是活动的应用场景
3.CachedThreadPool,是大小无界的线程池,适用于执行很多短期异步人物的小程序,或者是负载较轻的服务器
5.线程池创建多少线程是合理的
- CPU密集型,创建线程数是cpu核数+1
- I/O密集型,创建线程数是cpu核数*2
6.线程的监控
在我项目中,我通过在日志获取线程的一些指标数量来监控线程,主要通过以下的属性
- taskCount
- completeTaskCount
- LargestPoolSize
- getPoolSize
- getActiveCount
private void logThreaPoolInfo() {
int activeCount = executorService.getActiveCount();
long completedTaskCount = executorService.getCompletedTaskCount();
int corePoolSize = executorService.getCorePoolSize();
long keepAliveTimeSeconds = executorService.getKeepAliveTime(TimeUnit.SECONDS);
int largestPoolSize = executorService.getLargestPoolSize();
int maximumPoolSize = executorService.getMaximumPoolSize();
int poolSize = executorService.getPoolSize();
long taskCount = executorService.getTaskCount();
logger.info("ActiveCount -> [" + activeCount + "] CompletedTaskCount -> ["
+ completedTaskCount + "] CorePoolSize -> [" + corePoolSize
+ "] KeepAliveTimeSeconds -> [" + keepAliveTimeSeconds
+ "] LargestPoolSize -> [" + largestPoolSize
+ "] MaximumPoolSize -> [" + maximumPoolSize
+ "] PoolSize -> [" + poolSize + "] TaskCount -> ["
+ taskCount + "]");
}
——————————————————————————————————————
Executor框架
1.线程池和Executor框架有什么关系呢?
其实线程池的主要实现类是TheadPoolExecutor,它是Executor框架中最核心的类,Executor框架包括三部分:任务(Runable接口或Callable接口),任务的执行(执行的核心接口是Execotor),异步计算的结果(Future和实现Future接口的FutureTask类)
具体的使用示意图如下(该图片来自于《多线程编程艺术》书)
2.任务执行机制中Executor类图:
Executor:一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor(Runnable command)
ExecutorService:是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法
AbstractExecutorService:ExecutorService执行方法的默认实现
ScheduledExecutorService:一个可定时调度任务的接口
ScheduledThreadPoolExecutor:ScheduledExecutorService的实现,一个可定时调度任务的线程池
ThreadPoolExecutor:线程池,可以通过调用Executors以下静态工厂方法来创建线程池并返回一个ExecutorService对象
Exector只是一个接口,规范,声明,并没有具体的实现功能,需要制定接口的实现类来完成制定的功能,但是threadPoolExecutor类虽然是Executor的实现类,但是实例化需要传入很多的参数,考虑线程并发与线程池等参数,所以官方建议是使用Executors工厂类来创建线程池对象。
3.FutureTask讲解
FutureTask实现了Future接口,也实现了Runnable接口。它的方法get会使当前线程阻塞,当线程处于完成状态的时候会返回结果。cancel方法在不同的状态会呈现结果不同,在线程没有启动的时候,任务不会执行,当线程启动但是中断了,返回为true,不中断返回false,结束状态返回false。
它的具体实现是基于AQS,abstractQueuedSynchronizer,后续我们在专门研究它。
总结
本文先讲解到这里,希望对你有帮助!