本文共 4685 字,大约阅读时间需要 15 分钟。
简述:我们在使用线程的时候,去创建一个线程来执行是非常方便的,如果一个线程的执行时间比较短,而我们又需要创建大量的线程,那么再每次使用时都重新去创建一个线程,显然创建线程和最终使用完线程对线程进行销毁都是非常浪费资源的,因此,java当中就提供了线程池,在一个任务执行结束之后,另外的任务也可以继续使用这个线程进行执行。
在了解jdk提供的各种线程池之前,我们先来了解线程池当中基础属性的含义,以及线程池的一个大概执行流程
corePoolSize: 核心线程数,在我们新创建好一个线程池之后,里面是没有线程的,等到有任务提交过来,才会去创建线程,如果当前线程池当中的线程数小于corePoolSize(核心线程数),有新的任务进来就去创建线程执行,如果超过了核心线程数,就会将线程放在缓存队列当中。当然如果调用了preStartAllCoreThreads或者preStartCoreThread, 会在线程池创建之后,就创建核心线程数量的线程和一个线程。
maximumPoolSize: 最大线程数,线程池允许创建的最大线程数,如果缓存队列已经满了,并且当前创建的线程数小于maximumPoolSize(最大线程数),那么就会创建新的线程。当然,如果缓存队列是个无界队列,也就是说缓存永远不会满,那这个参数就没有意义。
keepAliveTime: 线程存活时间,表示在没有任务的情况下,线程保留多长时间会终止,默认情况下,只有当线程数大于corePoolSize, 同时有空闲线程,当空闲线程的空闲时间大于keepAliveTime, 就会终止,直到线程数量小于corePoolSize。如果在调用了allowCoreThreadTimeOut(true)方法,当线程数量小于corePoolSize,对于空闲的线程超过了keepAliveTime,也是会将线程终止,直到线程数量将为0
timeUnit: 线程存活时间的单位,
workingQueue: 保存线程的队列,当线程池中,线程的数量超过了corePoolSize,就会将新提交的线程保存在队列当中。
threadFactory: 线程工厂,用来创建线程
handler: 拒绝策略,当线程池中缓存队列已经满了,并且线程池中的线程数超过了最大线程数,有新的任务进来,就会执行拒绝策略:
ThreadPoolExecutor.AbortPolicy 丢弃任务,并抛出异常RejectedExecutionException
ThreadPoolExecutor.DiscardPolicy 也是丢弃任务,但是不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列中最前面的任务,尝试执行新的任务
ThreadPoolExecutor.CallerRunsPolicy: 由调用线程来执行提交的任务
了解了上面的一些参数信息,我们来看下java给开发者提供的一些线程池
我们先来看下他的源码
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); }
接下来调用的是ThreadPoolExector的一个构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
通过上面的代码可以看到,固定线程数量的线程池,核心线程数和最大线程数,都是传递过来的线程,存活时间是0,也就是如果线程空闲会立刻被终止掉。defalutHandler使用的是AbortPolicy
关于FixedThreadPool的执行流程
1、如果当前运行的线程数小于corePoolSize, 创建新的线程来执行任务
2、如果当前运行的线程数量大于corePoolSize, 新提交过来的任务,需要保存到缓存队列当中。
3、如果corePoolSize当中有执行结束的线程,就从缓存队列当中获取线程来执行。
--------
LinkedBlockingQueue是个无界队列(容量为Interger.MAX_VALUE),因此我们在使用固定线程数量的线程池就会有下面一些情况
1、当线程池中的线程数量达到corePoolSize后,新添加过来的任务都会放在队列当中。
2、由于队列是无界的,maximumPoolSize这个参数是无效的,
3、基于1和2,线程池当中最大的线程数就是corePoolSize
4、基于3,keepAliveTime 在默认情况下是无效的,因为不会有超过corePoolSize的线程数(当然如果调用了allowCoreThreadTimeOut(true)的话,是会将核心线程池当中空闲的线程立刻停止掉)
5、因为使用无界队列,线程池在没有执行shutDown或者shutDownNow, 不会拒绝任务。
适用场景:任务量的提交和处理相对来说比较稳定,不会某一时间提交大量任务,
创建代码
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())); }
SingleThreadExecutor的参数,只是在FixedThreadExecutor基础上,将线程数设置成了1,其他的都一样
对于SingleThreadExecutor的执行顺序
1、当前运行的线程数小于corePoolSize,创建线程去执行,
2、如果线程当中已经有一个运行中的线程,将任务加入到队列中去
3、运行中的线程执行结束之后,会从LinkedBlockingQueue中取任务来执行。
适用场景:对于任务量比较小的情况下,可以使用,或者是对于离线数据的处理,只需要一个线程就可以了,对于任务量很大的情况下,不要使用这个线程,会使得队列不断变长,最终会导致oom(内存溢出)
创建代码
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); }
先来看下它的参数,核心线程数是0,最大线程数,Integer.MAX_VALUE, 线程存活时间60秒,SynchronousQueue队列,没有容量的队列。
最大线程容量可以理解成无界,如果主线程提交任务的速度大于线程池当中处理线程处理任务的速度,那么CachedThreadPool将会不断创建新的线程,极端情况下,也是有可能把CPU占满的。
关于SynchronousQueue队列,里面是不存储元素的,每次offer进去的元素,会立刻返回,如果刚好有线程执行poll操作,offer则返回true, 否则返回false
1、初始化时,maximumPool为空,corePoolSize 是0,此时会创建一个新线程执行任务
2、步骤1 中的线程执行完之后,会执行SynchronousQueue.poll ,这个操作会让空闲线程最多在SynchronousQueue中等待60秒,如果60秒中主线程提交了一个任务,就会让空闲线程执行新任务,否则空闲线程会被终止。
总结:对于新提交的任务,会先执行SynchronousQueue.offer.如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll操作,刚好配对成功,就会把任务交给空闲线程去执行。否则就会创建新的线程去执行任务。
适用场景:某一时间有大量的任务需要处理,同时每个任务的处理时间不会太久,这种情况下,使用cachedThreadPool可以一定程度上提升性能。
先来看创建代码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); }
接下来调用ScheduledThreadPoolExecutor中的构造方法
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
在使用ScheduledThreadPool时,需要指定调用的频率,这里不对ScheduledThreadPool做详细介绍
在实际开发中,建议还是要基于ThreadPoolExecutor自定义自己的线程池,设定核心线程数,最大线程数,超时事件,以及拒绝策略。
转载地址:http://klvti.baihongyu.com/