java中的线程池
java的线程池是运用场景最多的并发框架。合理运用线程池有3个好处
- 降低资源消耗。降低线程创建和销毁的消耗
- 提高响应速度。当任务到达时,不需要等待线程创建完就能立即执行
- 提高线程的可管理性。使用线程池可以进行统一分配,调控和监控。
线程池的实现原理
处理流程如下:
- 线程池判断核心线程池里的线程是否都在执行任务。如果不是,创建一个新的工作线程来执行任务。如果是,则进入下一个流程。
- 线程池判断工作队列是否已满。如果没有满,则将新提交的任务存储在这个工作队列中,如果工作队列满了,则进入下一个流程。
- 线程池判断线程池的线程是否都处于工作状态,如果没有,则创建一个新的线程执行任务,如果慢了,则交给饱和策略来处理任务。
ThreadPoolExecutor执行execute方法分为下面4个情况
- 如果当前运行的线程小于corePoolSize,则创建新线程执行任务(这一步需要获取全局锁)
- 如果运行的此案成等于或大于corePoolSize,则将任务加入BlockingQueue
- 如果无法将任务加入BlockingQueue,则创建新的线程处理
- 如果创建新的线程会使得当前运行的线程大于maximumPoolSize,则任务被拒绝,执行rejectedExecution()方法
线程的使用
线程池的创建
new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,millisseconds,runnableTaskQueue,handler)
参数的作用
corePoolSize
:核心大小数runnableTaskQueue
:任务队列,用于保存等待执行的阻塞队列,可以选择下面几个ArrayBlockingQueue
:基于数组是我有界阻塞队列,按照FIFO排序LinkedBlockingQueue
:基于链表的阻塞队列,吞吐量高于ArrayBlockingQueue
,静态工厂方法Executors.newFixedThreadPool
使用了这个队列SynchronousQueue
:不存储元素的阻塞队列,Executors.newCachedThreadPool
使用了这个队列PriorityBlockingQueue
:具有优先级的无限阻塞队列- DelayQueue:DelayQueue封装了一个PriorityQueue,这个queue会对队列中的ScheduledFutureTask进行排序。time小的放到前面
maximumPoolSize
:线程池最大数量。如果队列满了,并且已创建的线程数小于最大线程数,则线程池创建新的线程执行任务。ThreadFactory
:用于设置创建线程的工厂,通过线程工厂给每个创建出来的线程可以设置名称。RejectedExecutionHandler
:饱和策略。当队列和线程都满了,说明线程池处于饱和状态,必须采取一种策略处理提交的新任务,默认是AbortPolicy。下面是java的4中策略AbortPolicy
:直接抛出异常CallerRunsPolicy
:只用调用者所在的线程来执行任务- 丢弃队列里最近的一个任务,来执行当前任务
DiscardPolicy
:不处理,丢弃掉。- 或者自定义策略,记录日志等。
keepAliveTime
:线程活动保持时间。线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,每个任务执行的时间比较短,可以跳大事件,提高线程的利用率。TimeUnit
:线程活动保持时间的单位。
向线程池提交任务
两个方法,execute和submit
execute用于提交不需要返回的任务。
submit用于提交需要返回的任务。线程池会返回future对象,通过future.get()获取返回值。get()方法会阻塞到任务完成。
如何合理的配置线程池
- CPU密集型的应配置尽可能小的线程。如N(CPU) + 1个线程的线程池。
- IO密集的任务县城不一定一直在执行任务,则配置尽可能多。如2 * N(CPU)
线程的监控
线程监控可以使用以下的属性
taskCount
:线程池需要执行的任务数量completedTaskCount
:线程池在运行过程中已完成的任务数量,小于或等于taskCountlargestPoolSize
:线程池里曾经创建过的最大线程数量,通过这数据可以知道线程池是否曾经满过。getActiveCount
:获取活动的线程数。- 通过继承线程池来自定义线程池,重写线程池的beforeExecute,afterExecute,terminated方法。