一、jdk线程池
1.java中的线程池(ThreadPoolExecutor)
线程池顾名思义就是存放线程的池子。通过线程池创建线程可以重复利用,减少频繁创建线程所带来的系统资源消耗。
//这只是ThreadPoolExecutor类其中一个构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
corePoolSize:核心线程池数,也叫最小线程数。
maximumPoolSize:最大线程数。
keepAliveTime:空闲线程最大存活时间。
unit:时间单位。
BlockingQueue:阻塞队列。
RejectedExecutionHandler:拒绝执行处理器。
ThreadPoolExecutor类代表java中的线程池。当新创建一个线程池的时候默认不会创建线程,当需要线程的时候才创建线程,直到线程池中的线程数量达到最小线程数,之后再有请求需要线程的时候,会将请求存放到队列中(队列分为有界队列ArrayBlockingQueue和无界队列linkedBlockingQueue),当队列中的请求也存不下的时候,会判断线程池中的线程数是否达到指定的最大线程数,小于最大线程数,就会继续创建线程,等于最大线程数时就不会创建新的线程了,而是执行拒绝策略。如果线程池中的队列是无界队列,最大线程数是没有意义的,因为当线程数达到最小线程数时,会将请求一直往无界队列中存储。无界队列的最大值为Integer的最大值,理论上可以理解为无界队列。
2.创建线程池的方式
java中封装了一个工具类Excutors,可以通过它创建不同类型的线程池,但是底层也是直接通过ThreadPoolExecutor的构造器创建线程池,只不过默认给上面的属性中设置了相应的值而已。
(1)固定线程数的线程池(newFixedThreadPool)
这种线程池里面的线程数量是固定的,具体的线程数可以考虑设置成CPU核数*N(N取决于并发的线程数和计算机的硬件资源)。
//获取CPU核数
int processors = Runtime.getRuntime().availableProcessors();
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory);
}
FixedThreadPool中的线程实例会被复用,这些固定数量的线程处理一个共享的无界队列。任何时间点最多有固定数量的线程处于活动状态执行任务。如果所有的线程都处于活动状态,有多的任务被提交过来,它就会一直在无界队列中等待直到有线程可用。如果任何线程在执行过程中遇到错误而终止,就会有新的线程替代它继续执行后续的任务。所有的线程都会一直存在于线程池中,直到显示的执行ExecutorService.shutdown()方法关闭。因为阻塞队列使用了LinkedBlockingQueue,这是一个无界队列,所以永远不会拒绝任务。LinkedBlockingQueue在入队和出队的时候使用是不同锁,意味着入队和出队不存在互斥的关系,在多CPU的情况下,能同时入队和出队,做到真正的并行。因此这种线程池永远不会拒接任务,也不会开辟新的线程,也不会因为线程长时间不用而销毁线程。这种线程池适合于固定且稳定的并发场景。
(2)缓存线程池(newCachedThreadPool)
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue(),
threadFactory);
}
这种线程池的核心池数量为0,最大线程数为Integer的最大值,这就意味着任务一提交过来就会加入到阻塞队列中并且不会存储在队列中,如果没有空闲的线程就会创建新的线程去执行该任务。当线程池中的线程空闲60秒就会被销毁。阻塞队列SynchronousQueue,也就是入队和出队是同一个锁,两者在执行过程中需要等待否则就会阻塞。特点总结如下:
(1)这是一个可以无限扩大的线程池。
(2)适合执行处理时间比较小的任务。
(3)线程空闲60S就会被销毁,所以长时间处于空闲状态的时候,该线程池几乎不占用资源。
(4)阻塞队列中没有存储空间,只要有任务来就必须找到一个空闲的线程去执行任务,如果没有空闲的线程可用,就会创建新的线程去执行。如果主线程提交任务的速度远远大于CachedThreadPool执行任务的速度,CachedThreadPool就会不断的去创建新的线程,可能会导致系统资源耗尽,所以一定要注意控制线程并发的数量。
(3)单个线程的线程池(newSingleThreadExecutor)
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(),
threadFactory));
}
SingleThreadExecutor通过单个线程去执行任务,它的核心线程池数量和最大线程池数量均被设置成1,使用的也是无界队列。所以它也永远不会去拒绝执行任务,当线程在运行时遇到错误时,会创建新的线程继续执行后续的任务。创建一个单线程化的线程池,他只用唯一的工作线程来执行任务,保证所有的任务按照指定的顺序执行,所以它比较适合那些需要按序执行任务的场景。
(4)定时周期性执行任务的线程池(newScheduledThreadPool)
这种类型的线程池也是固定个数的线程池,相比于ThreadPoolExecutor它的强大之处在于可以延迟执行任务、执行又返回值的任务、定时周期性的去执行任务。