前言:
在前面的例子中,我们都是手动去实现Runbale、Callable或者继承Thread类来创建一个线程的,但是在实际的开发中,我们通常不会这样做,而是使用线程池,将创建线程的动作交给线程池我们直接拿来用就可以了,线程池相似于数据库连接池,线程池里从放了一定数量已经创建好的线程,我们直接拿来用就可以了,至于线程的销毁什么的都不用我们操心,线程池会负责。
一、为什么使用线程池:
在我们的实际开发中,线程的创建和销毁都是一件相当耗费资源的工作,并且多线程之间之间的切换执行也是非常消耗资源,因此引入了线程池,使用已经存在的线程,消除了线程创建时的耗时,且可以通过设置线程的数量,防止资源的浪费。
二、线程池类的关系图:
我们来看一张关于线程池类之间的关系图,其中线程池的顶级接口Executor,在Executor接口中只是提供了一个执行任务的execute()方法,而下面的接口和类都是去继承他和实现它的,同时做了一写扩展。
1、ExecutorService接口:
从上面看ExecutorService接口直接继承了Executor,下面是ExecutorService接口中的一些方法:我们可以看到ExecutorService在Executor接口原有的功能上有扩展了很多功能,极大的增强了Executor的功能,不仅支持有返回值的任务执行,而且还有很多十分有用的方法来为你提供服务。
2、ScheduledExecutorService接口
ScheduledExecutorService接口是继承了ExecutorService接口,ScheduledExecutorService接口中的方法相对来说很少,但是却加了特有的调度(schedule)具体方法可以看一下下图:
3、AbstractExecutorService类:
AbstractExecutorService实现了ExecutorService接口,只是实现了ExecutorService的方法吗,这个没什么好说的,这里也就不截图一笔带过了。
4、ThreadPoolExecutor类:
ThreadPoolExecutor类继承了AbstractExecutorService类,在这里ThreadPoolExecutor勒种的方法相当的繁多和复杂,但本质上来说还是对AbstractExecutorService的扩展,并且实现了一些自己特有的方法,例如截图中实现了Executor接口中的excute()方法,重写了AbstractExecutorService中的shutdown方法:
5、ScheduledThreadPoolExecutor类:
ScheduledThreadPoolExecutor在实现上继承了ThreadPoolExecutor,所以我们依然可以将ScheduledThreadPoolExecutor当成ThreadPoolExecutor来使用,但是从源码中我们可以看出,ScheduledThreadPoolExecutor的功能要强大得多,因为ScheduledThreadPoolExecutor可以根据设定的参数来周期性调度运行,下面的图片展示了四个和周期性相关的方法,当然这只是其中一些方法,ScheduledThreadPoolExecutor还有很多的方法,在这里只是作为了解一下不做过多赘述。
二、ThreadPoolExecutor:
在java中创建线程池,实际上都是调用了ThreadPoolExecutor类中的构造方法,我们来看一下该构造方法都有哪些参数:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
corePoolSize:线程池中长期维护的线程数量。
maximumPoolSize:线程池中能拥有的最大线程数量。
workQueue:用于缓存任务的阻塞队列,对于不同的场景采用不同的策略,常用的策略有两种:
SynchronousQueue<Runnable>:此队列不会缓存任务,如果向线程池中提交任务,线程池并没有空闲的线程来执行任务,则入列操作会阻塞,当有线程获取任务出列操作会唤醒 入列操作的线程,从这个特性来看SynchronousQueue是一个无界的队列,因此当使用SynchronousQueue时作为线程池的阻塞队列时,参数maximumPoolSize不会起作用。
LinkedBlockingQueue<Runnable>:此队列的实现时一个链表结构,可以使有界的也可以是无界的,Executor接口中时无界的
keepAliveTime:表示空闲线程的存活时间。
unit:表示keepAliveTime的单位。
handler:表示当workQueue已满,且线程池中的线程数量打到maximumPoolSize时,线程池拒绝添加新任务采用的策略一般有一下四种取值:
ThreadPoolExecutor.AborPolicy():抛出RejectedExecutionException异常。
ThreadPoolExecutor.CallerRunsPolicy():由向线程池提交任务的线程
ThreadPoolExecutor.DiscardOldestPolicy():抛弃最旧的任务(最先提交而没有执行的任务)
ThreadPoolExecutor.DiscardPolicy():抛弃当前任务、
注:在jdk1.8中ThreadPoolExecutor的构造函数中并没有要求传入handler参数,而是在调用构造参数时,默认了ThreadPoolExecutor.AborPolicy()策略
三、四大线程池:
ThreadPoolExecutor的构造函数有很多参数,用起来很不方便为了方便的创建线程池,javaEE中为我们提供了Executors工具类,Executors提供了四种创建线程池的方法,分别如下
1、newCachedThreadPool:
该方法可以创建一个可缓存的线程容器,如果线程池的线长度超过处理需求,可灵活的回收空闲线程,若无可回收,则创建新的线程:
该线程池的特点:
(1):工作线程的创建数量几乎没有限制(其实也是有受限制的,数目为Interger.MAX_VALUE)
(2):空闲的工作线程会自动销毁,有新任务时在创建。
(3):使用CachedThreadPool时,一定要控制无人数量,否则容易造成大量创建线程,造成系统瘫痪。
2、newFixedThreadPool:
该方法创建一个指定数目的线程池,没提交一个任务时就会创建一个工作线程,当线程数达到最大数量时,则将提交的任务存放到池队列中。
优点:提高程序效率和节省创建线程的的开销。
缺点:在线程池空闲时,即线程池中没有任务运行时,他不会释放工作线程,还会占用一定的系统资源。
3、newSingleThreadExecutor:
该方法创建一个单列的Executor,即只创建一个唯一的工作线程来执行任务,保证所有的任务按照(FIFO,LIFO,优先级)执行,如果这个线程出异常,会有另一个线程来取代它,保证顺序执行。
工作线程最大的特点是保证任务的执行顺序,并且在任意时间点不会有多余的活动线程。
4、newScheduleThreadPool:
该方法创建一个定长的线程池,而且支持定时以及周期性执行任务,