一、线程池的概念
1.1线程池是什么
官方解释:线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。
个人理解:顾名思义,线程池就是装线程的容器呗,只要我们设定好参数,这个池子可以按照我们的要求,帮我们自动创建线程、管理线程,我们不必再自己new一个thread,也不用再花精力去考虑每个线程的生命周期。
1.2 为什么使用线程池
创建一个线程的开销是比较大的,因为这涉及到和操作系统的交互,如果我们创建的很多个线程都是为了很生命周期短暂、相对简单的这种任务,完成任务后就把线程销毁,就显得十分浪费资源。另一方面,自己创建的线程如果太多,管理起来将会十分繁琐。为了应对这种问题,我们就可以使用线程池。线程池的使用也是比较简单,配置好参数之后,我们向线程池提交任务(runnable),线程池就会按照自己的策略自动开始处理任务。
二、JAVA中的线程池
在JAVA中,我们可以使用ThreadPoolExecutor来创建线程池,这个类的构造函数如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
2.1几个重要参数和概念的解释
上文中提到了JAVA中可以使用ThreadPoolExecutor来创建线程池,从构造方法中,可以看到有7个参数,接下来对其一一解释。
2.1.1 corePoolSize核心线程数
核心线程,指的是在一般情况下,一直存在、不会被销毁的线程,也就是说即使任务已经完成,闲置着,这个线程也不会被销毁。
与核心线程相对的,就是普通线程,普通线程是指完成任务后,闲置时间超过一定时长后,就会被回收的这种线程。
线程池刚创建时,没有任务需要处理,也没有线程。当任务陆续提交到线程池,就会开始创建线程,刚开始只要当前线程数不够用,就会创建线程,但是当线程池中的线程数目达到 corePoolSize后,如果依旧有新来的任务,就不会再立刻创建线程而是先被添加到缓存队列中。所以corePoolSize这个参数,就是指核心线程的数量,例如我们设置核心线程数为10,那么就意味着线程池中刚开始只要有需要就会创建最多10个线程,而且在完成任务之后,这10个线程依旧会“随时待命”,它们会一直等待新任务,不会被销毁。
2.1.2 maximumPoolSize最大线程数量
这个参数的意思是线程池中能够容纳同时执行的最大线程数。如果线程池中的线程数已经达到corePoolSize,并且缓冲队列也满了,这时就要看线程池中当前已经存在的线程数是否大于 maximumPoolSize,如果小于maximumPoolSize ,则会继续创建线程去执行任务, 否则将会调用去相应的任务拒绝策略来应对这个任务。 比如,如果我们设置了maximumPoolSize=50,如果此时有超多任务需要线程来执行,但现在缓冲队列已经满了了,而且线程池中已经创建了50个线程都在处理这些任务,实在没有空闲线程去处理多余任务,但即使在种情况下,线程池也不会再去创建更多线程了,因为我们限制了maximumPoolSize=50,没办法,线程池只能启用拒绝策略,至于拒绝哪个任务,怎么拒绝,后面在线程池的拒绝策略中会详细介绍。另外,maximumPoolSize此值必须大于等于1。
2.1.3 keepAliveTime线程最大空闲时间
上文说到,普通线程在完成任务之后会闲置,闲置的一段时间后,如果依旧没有任务,就会销毁,这里的“一段时间”,就是指的keepAliveTime。还有一个参数TimeUnit,是用来指定时间单位,比如我们设置keepAliveTime=60,TimeUnit=TimeUnit.SECONDS,就是指线程最大空闲时间为60秒。时间单位除了SECONDS之外,还有NANOSECONDS(纳秒),MICROSECONDS(微妙),MILLISECONDS(毫秒),MINUTES(分钟)、HOURS(小时)、DAYS(天)等。
2.1.4 BlockingQueue<Runnable>任务缓冲队列
上文中我们说道,线程池创建之初,如有多个任务,线程池会创建新的线程去处理,但如果创建的线程已经达到了corePoolSize,依然不能满足需求,就会把多余的任务暂时放到一个缓冲队列(也就是BlockingQueue<Runnable>)中,等待空闲线程来处理。我们可以使用以下几种阻塞队列充当任务缓冲队列:
ArrayBlockingQueue :由数组结构组成的有界阻塞队列
LinkedBlockingQueue :由链表结构组成的有界阻塞队列(常用)
PriorityBlockingQueue :支持优先级排序的无界阻塞队列
DelayQueue: 使用优先级队列实现的无界阻塞队列
SynchronousQueue: 不存储元素的阻塞队列(常用)
LinkedTransferQueue: 由链表结构组成的无界阻塞队列
LinkedBlockingDeque: 由链表结构组成的双向阻塞队列
2.1.5 threadFactory线程工厂
线程工厂,用来为线程池创建线程,若我们不指定线程工厂,线程池内部会调用Executors.defaultThreadFactory()使用默认的线程工厂,该工厂创建的线程优先级都是Thread.NORM_PRIORITY。如果我们指定线程工厂,我们可以对产生的线程进行一些操作。
2.1.6 RejectedExecutionHandler拒绝策略
上文说到,如果提交的任务过多,缓冲队列都满了。而且线程池中的正在执行任务的线程已经达到最大maximumPoolSize,实在无法再处理这么多任务了,线程池就会执行拒绝策略,有如下几种拒绝策略可供选择:
DiscardOldestPolicy :抛弃最早进来的任务
AbortPolicy:直接丢弃任务,并抛出RejectedExecutionException异常
CallerRunsPolicy:哪个线程传过来的这个任务,就丢给哪个线程自己去处理
DiscardPolicy:直接丢弃任务,但是不抛出异常
2.2 线程池工作原理
其实在2.1节介绍线程池各个参数的含义时,就已经大致描述了线程池的工作流程,但是比较零散,不够系统,接下来,我们用流程图的方式来描述线程池的工作原理:
三、线程池的使用
3.1 execute()和submit()
我们在创建好线程池后,使用execute()和submit()都可以向线程池中添加任务来执行,但这两个方法也是有区别的。通过源码,我们可以看出,execute方法需要传入runnable,并且没有返回值。
而submit方法不仅可以接受Runnable,还能接受Callable,并且能够有Future类型的返回值。
概括一下,execute()和submit()方法有以下区别:
- 接受的参数不同
- submit()有返回值,能够获取到执行的结果等信息,而execute()没有返回值;
- submit()方便异常处理,如果在任务中里会抛出异常,我们又希望外面的调用者能够感知这些异常并做出及时的处理,那么就需要用到submit(),通过捕获Future.get()抛出的异常来进行处理。
3.2 使用线程池处理任务
本节我们来实际运用一下线程池。我们先新建500个任务,然后提交到线程池中,观察输出结果。相关源码如下:
package com.example.threadtest.threadpool;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
private static ArrayList<Runnable> runnableArrayList = new ArrayList<>();
private static AtomicInteger index = new AtomicInteger(0);
public static void main(String[] args) {
createRunnable();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(20), new ThreadFactory() {
@Override
public Thread newThread(@NotNull Runnable runnable) {
return new Thread(runnable);
}
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable runnable, ThreadPoolExecutor threadPoolExecutor) {
threadPoolExecutor.shutdown();
throw new RejectedExecutionException("Task " + runnable.toString() + " rejected from " + "异常抛出时间:" + threadPoolExecutor.toString() + System.currentTimeMillis());
}
});
for (int i = 0; i < runnableArrayList.size(); i++) {
//使用execute()执行任务
// threadPoolExecutor.execute(runnableArrayList.get(i));
//使用submit()执行任务
Future<?> future = threadPoolExecutor.submit(runnableArrayList.get(i));
}
}
private static void createRunnable() {
for (int i = 0; i < 500; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "处理了任务" + index.incrementAndGet() + ",时间:" + System.currentTimeMillis());
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
runnableArrayList.add(runnable);
}
}
}
9:25:58: Executing task 'Main.main()'...
Executing tasks: [Main.main()] in project D:\project\wuhan\MyApplication2
> Task :ThreadTest:compileKotlin
> Task :ThreadTest:compileJava
> Task :ThreadTest:processResources NO-SOURCE
> Task :ThreadTest:classes
> Task :ThreadTest:Main.main()
Thread-0处理了任务1,时间:1626830758968
Thread-8处理了任务5,时间:1626830758968
Thread-7处理了任务4,时间:1626830758968
Thread-6处理了任务3,时间:1626830758968
Thread-3处理了任务2,时间:1626830758968
Thread-9处理了任务9,时间:1626830758969
Thread-1处理了任务10,时间:1626830758969
Thread-4处理了任务8,时间:1626830758969
Thread-10处理了任务11,时间:1626830758969
Thread-2处理了任务7,时间:1626830758969
Thread-5处理了任务6,时间:1626830758968
Thread-14处理了任务12,时间:1626830758969
Thread-11处理了任务13,时间:1626830758969
Thread-13处理了任务14,时间:1626830758969
Thread-12处理了任务15,时间:1626830758969
Thread-19处理了任务16,时间:1626830758969
Thread-16处理了任务17,时间:1626830758969
Thread-17处理了任务18,时间:1626830758969
Thread-18处理了任务19,时间:1626830758969
Thread-15处理了任务20,时间:1626830758969
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@7f31245a rejected from 异常抛出时间:java.util.concurrent.ThreadPoolExecutor@6d6f6e28[Shutting down, pool size = 20, active threads = 20, queued tasks = 20, completed tasks = 0]1626830758969
at com.example.threadtest.threadpool.Main$2.rejectedExecution(Main.java:40)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:112)
at com.example.threadtest.threadpool.Main.main(Main.java:47)
Thread-16处理了任务21,时间:1626830763976
Thread-8处理了任务22,时间:1626830763976
Thread-19处理了任务23,时间:1626830763978
Thread-1处理了任务24,时间:1626830763978
Thread-9处理了任务26,时间:1626830763978
Thread-11处理了任务27,时间:1626830763978
Thread-13处理了任务25,时间:1626830763978
Thread-18处理了任务34,时间:1626830763978
Thread-4处理了任务33,时间:1626830763978
Thread-10处理了任务38,时间:1626830763979
Thread-0处理了任务32,时间:1626830763978
Thread-7处理了任务31,时间:1626830763978
Thread-6处理了任务30,时间:1626830763978
Thread-2处理了任务29,时间:1626830763978
Thread-5处理了任务28,时间:1626830763978
Thread-15处理了任务40,时间:1626830763979
Thread-3处理了任务39,时间:1626830763979
Thread-14处理了任务37,时间:1626830763979
Thread-17处理了任务36,时间:1626830763979
Thread-12处理了任务35,时间:1626830763978
> Task :ThreadTest:Main.main() FAILED
通过log,我们看到在处理完任务20时,由于当前没有空闲线程能够处理新任务,也不能再新建线程,并且任务队列已经满了,所以再有新任务提交进来,就直接抛出了异常,线程池不再接受新的任务,但是在抛异常之前就已经添加进来的任务,还是会处理完。当然,还有很多任务没有处理完,线程池抛出了异常,也从一定程度上反映出我们线程池参数配置的不太合理。
总结
本文主要介绍了JAVA线程池的相关知识,如有错误还请各位大神指出,同时也欢迎各位大佬来讨论和交流技术,本人邮箱hbutys@vip.qq.com