1.什么是线程池
线程池就是由很多线程,加上一个任务(实现了Runnable接口的类)队列组成,线程池中的线程,自己可以定义,当向线程池中发任务时,如果有线程空闲,线程就将任务带给CPU处理,如果没有线程空闲,就存储在任务队列中。
2. 为什么要用线程池
1)可以提高效率,当有很多的线程需要执行的时候,需要频繁的创建和销毁线程,很大程度上影响处理效率,利用线程池,可以不用频繁的创建和销毁线程,重用存在的线程,减少线程的创建,提高性能。
2)能够有效控制线程的最大并发数,防止因为系统资源不足,而产生阻塞的情况。
3)能够简单的管理线程,如进行延时、定时执行等。
3.线程池底层原理
java中,有一个Executor接口,是线程池的顶级接口,这个接口只有一个execute(Runnable r)方法,execute方法在未来某个时间执行给定的命令。该命令可能在新的线程、已入池的线程或者正调用的线程中执行,这由 Executor 实现决定,执行execute方法,一般来说,Runnable任务开辟在新线程中的使用方法为:new Thread(new RunnableTask())).start(),但在Executor中,可以使用Executor而不用显示地创建线程:executor.execute(new RunnableTask()),异步执行,也就是说,Executor可以替代Thread的创建,java线程池,就是利用这个Executor接口实现的,Executor就相当于一个出租车,而线程池就相当于一个出租车公司。
public class Thread5 implements Executor {
public static void main(String[] args) {
new Thread5().execute(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
@Override
public void execute(Runnable command) {
new Thread(command).start();
}
}
4.线程池的创建
在java中,可以使用Java.util.concurrent.Executors这个类来进行创建
ExecutorService service = Executors.newFixedThreadPool(5);//固定线程数的线程池
底层源码如下
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
从源码也可以看出,线程池的创建是通过new ThreadPoolExecutor这个类来创建的,通过Executors这个类创建的线程池是jdk提供的,实际使用中,可能并不适用实际情况,所以可以通过ThreadPoolExecutor这个类来创建合适的线程池。下面来看一下ThreadPoolExecutor这个类中的构造方法
//五个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
//六个参数的构造函数-1
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
//六个参数的构造函数-2
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
//七个参数的构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
int corePoolSize => 该线程池中核心线程数最大值
核心线程:线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程,核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态),如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉,很好理解吧,正常情况下你不干活我也养你,因为我总有用到你的时候,但有时候特殊情况(比如我自己都养不起了),那你不干活我就要把你干掉了。
int maximumPoolSize
该线程池中线程总数最大值,线程总数 = 核心线程数 + 非核心线程数。核心线程在上面解释过了,这里说下非核心线程:不是核心线程的线程。
long keepAliveTime
该线程池中非核心线程闲置超时时长,一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉, 如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。
TimeUnit unit
keepAliveTime的单位,TimeUnit是一个枚举类型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天
BlockingQueue workQueue
该线程池中的任务队列:维护着等待执行的Runnable对象,当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。
常用的workQueue类型:
-
SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大。
-
LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
-
ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
-
DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
ThreadFactory threadFactory
创建线程的方式,这是一个接口,你new他的时候需要实现他的Thread newThread(Runnable r)方法,一般用不上,默认即可
小伙伴应该知道AsyncTask是对线程池的封装吧?那就直接放一个AsyncTask新建线程池的threadFactory参数源码吧:
new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread Thread(Runnable r) {
return new Thread(r,"AsyncTask #" + mCount.getAndIncrement());
}
}
这么简单?就给线程起了个名
RejectedExecutionHandler handler
拒绝策略,当工作队列、线程池全满时如何拒绝新任务,默认抛出异常。
新建一个线程池的时候,一般只用前五个参数
corePoolSize,maximumPoolSize,workQueue之间关系。
当线程池中线程数小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
当线程池中线程数达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 。
当workQueue已满,且maximumPoolSize > corePoolSize时,新提交任务会创建新线程执行任务。
当workQueue已满,且提交任务数超过maximumPoolSize,任务由RejectedExecutionHandler处理。
当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收这些线程。
当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收。
通过ThreadPoolExecutor.execute(Runnable command)方法即可向线程池内添加一个任务,execute方法的执行流程图如下:
jdk提供的线程池
- FixedThreadPool,通过Exectuors这个类来创建,线程池中只能创建固定数量的核心线程数,当线程池中线程数量达到核心线程数,就将任务放到队列中,队列满了,就会报错,但是,队列一般不会满,容量为Integer.MAX_VALUE
//Executors类中源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- CachedThreadPool ,可缓存线程池,线程数无限制,自动扩容。容量管理策略:如果线程 池中的线程数量不满足任务执行,创建新的线程。每次有新任务无法即时处理的时候,都会 创建新的线程。当线程池中的线程空闲时长达到一定的临界值(默认 60 秒),自动释放线程。默认线程空闲 60 秒,自动销毁。
//Executors类中,创建CachedThreadPool方法
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//
- ScheduledThreadPool,支持定时及周期性任务执行。使用的是DelayQueue
//Executors类中newScheduledThreadPool(int corePoolSize)方法
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ExecutorThreadPool源码
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
- SIngleThreadPool单线程化的线程池:有且仅有一个工作线程执行任务,所有任务按照指定顺序执行,即遵循队列的入队出队规则
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
//源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
线程池的创建有很多方式,上述方式其实还是通过ThreadPoolExecutor这个类创建的,ThreadPoolExecutor,这个类继承了AbstractExecutorService(抽象类),AbstractExecutorService实现了ExecutorService接口,ExecutorService实现了Executor接口,创建线程池的类,都要实现Executor接口。
ExecutorService,这个接口实现了Executor,可以理解为线程池服务类型,所有的线程池类都实现了这个接口,实现这个接口,就拥有了线程池的能力,这个接口中有管理线程池的方法,常用方如下:
- shutdown():优雅的关闭线程池,等对列中的任务都执行完之后,才可以关闭,
- List shutDownNow(): 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
- isShutdown:是否执行过shutdown方法,执行过就返回true,没有执行过就返回false。
- isTerminated:线程池是否关闭
- Future submit(Callable task): 提交一个返回值的任务用于执行,返回一个表示任务的未来结果的 Future。
- Future<?> submit(Runnable task): 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
Future 未来结果,代表线程任务执行结束后的结果。获取线程执行结果的方式是通过 get 方法获取的。get 无参,阻塞等待线程执行结束,并得到结果。get 有参,阻塞固定时长,等待 线程执行结束后的结果,如果在阻塞时长范围内,线程未执行结束,抛出异常。 常用方法: T get() T get(long, TimeUnit)
Callable 可执行接口。 类似 Runnable 接口。也是可以启动一个线程的接口。其中定义的方法是 call。call 方法的作用和 Runnable 中的 run 方法完全一致。call 方法有返回值。 接口方法 : Object call();相当于 Runnable 接口中的 run 方法。区别为此方法有返回值。 不能抛出已检查异常。 和 Runnable 接口的选择 - 需要返回值或需要抛出异常时,使用 Callable,其他情况可 任意选择。
参考:
线程池,这一篇或许就够了
通俗易懂,各常用线程池的执行 流程图
如有不足之处,欢迎指正,谢谢!