1:为什么要使用线程池
线程对于进程来说,虽然已经十一种轻量级的工具,但是线程的创建和销毁还是会消耗资源,这个资源包括时间和空间。
对于线程的使用还是要掌握一个度,在有限范围内增加线程的数量可以明显的提高系统的吞吐量,但是以但超过了某个阈值,就可能会拖垮系统。
2:什么是线程池
为了避免系统频繁的创建和销毁线程,我们可以让创建的线程复用。
线程池和数据库连接池都是类似的概念
3:jdk对线程池的支持
强大的jdk当然有线程池的实现
jdk提供了一套Executor框架,帮助开发人员进行线程控制,其本质就是一个线程池,核心成员如下:
其中Executors扮演者线程池工厂的角色,通过此工厂可以获得一个特定的线程池。
Executor提供了各种线程池的实现,比较常用的是一下工厂方法得到的线程池:
- public static ExecutorService newFixedThreadPool(int nThreads)
该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变。该线程池有一个任务队列,如果线程池中有空闲线程,就会到该任务队列中取一个任务执行
-public static ExecutorService newSingleThreadExecutor()
该方法返回一个只有一个线程的线程池。若大于一个任务背提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务
-public static ExecutorService newCachedThreadPool()
该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但是如果有空闲线程可以复用,则会优先复用这些空闲的线程。若没有空闲的线程,但是又有新的线程提交,就会创建新的线程处理。
- public static ScheduledExecutorService newSingleThreadScheduledExecutor()
该方法返回一个ScheduledExecutorService对象,线程池大小为一。ScheduledExecutorService在ExecutorService接口上扩展了在给定时间执行某任务的功能,如在某个固定的时延之后执行,或者周期性执行某个任务
-public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
该方法返回一个ScheduledExecutorService对象,但可以指定该线程池的线程数量。
3.1:非计划任务线程池
以newFixedThreadPool(int nThreads)为例:
3.2:计划任务线程池
该方法返回一个ScheduledExecutorService对象,可以根据时间需要对线程进行调度。
newSingleThreadScheduledExecutor() 方法返回的也是这个对象
该对象有三个主要的方法:
-public ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit unit);
schedule()会在给定时间,对任务进行一次调度。
- public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
-
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit);
4:线程池的核心实现
上面的工厂方法基本调用了下面的构造函数返回一个线程池对象
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数含义如下:
corePoolSize:指定了线程池中的线程数量maximumPoolSize:指定了线程池中最大线程数量keepAliveTime:当线程的数量超过corePoolSize时,多余的线程的存活时间,即多余的线程多久之后应该被销毁unit:时间单位workQueue:任务队列,被提交但是没有被执行的任务threadFactory:线程工厂,用于创建线程,一般默认即可handler:拒绝策略。任务太多来不及执行时,如何拒绝任务
这些参数中,除了任务队列和拒绝策略,其他的都容易理解
4.1:任务队列
根据队列功能分类,钩爪函数中可以使用以下几种队列
- 直接提交的队列
该功能由SynchronousQueue对象提供。SynchronousQueue没有容量,每次插入操作都要等待一个响应的删除操作,反之每一个删除操作都要等待一个插入操作。使用SynchronousQueue提交的任务并不会被真实的保存,而每次总是将新任务交给线程执行,如果没有空闲的线程,就尝试创建新的线程,如果线程数量达到最大值,就执行拒绝策略。所以使用这种队列时,如果最大线程数不是很大很容易执行拒绝策略
- 有界的任务队列
有界的任务队列可以使用ArrayBlockingQueue实现。ArrayBlockingQueue的构造函数必须带有一个容量参数,表示该队列得到最大容量。当使用该队列时,如果实际线程数小于corePoolSize,就会优先创建线程,若大于corePoolSize,则会将任务加入等待队列。如果等待队列已满,无法加入,则在线程数量不超过maximumPoolSize的前提下创建新线程,否则执行拒绝策略。可见,只有任务队列已满,线程数量才会超过corePoolSize
- 无界的任务队列
无界的任务队列可以通过LinkedBlockingQueue实现。与有界队列相比,除非系统资源耗尽,否则不会存在任务入队列失败的情况。当新任务来到,线程数小于corePoolSize时,线程池会增加新线程执行任务,但当系统达到corePoolSize后,就不会继续增加了。后续的任务只能使用空闲线程或者加入任务队列。
- 优先的任务队列
带有执行优先级的队列。它通过PriorityBlockingQueue实现,可以控制任务执行的先后顺序。
4.2:拒绝策略
最后一个参数handler指定了拒绝策略。JDK有四种内置的拒绝策略。