聊聊Java中的线程池(ExecutorService)
本章节的源代码位于gitee上,想要下载的请点击线程池
为什么要使用线程池
聊到线程,就要谈谈什么是进程,它和线程有什么关系呢?
进程:
- 每个程序启动都会开启一个进程
- 系统进行资源调度的基础
- 每个进程都在内存中有一块独立的空间
- 进程里的方法区,是用来存放进程中的代码片段的,是线程共享的
- 在多线程 OS 中,进程不是一个可执行的实体,即一个进程至少创建一个线程去执行代码
线程:
- CPU 调度和分派的基本单位
- 不能独立存在
- 线程不是一直执行的
- 线程使用的内存是所在进程的内存
- 线程只拥有在运行中必不可少的资源(如程序计数器、栈)
- 程序计数器用于标记该时间片结束后,程序执行到的位置,方便下一次继续往后执行
- 每个线程都有自己独立的栈资源,其他线程无权访问。用于存放该线程的局部变量和调用栈帧
关系:
- 一个程序至少一个进程,一个进程至少一个线程,进程中多个线程共享进程资源
- 启动Java中的main函数就会启动一个线程,该线程称之为主线程。同时还会启动一个gc线程。
- 一个进程有多个线程,多个线程共享进程的堆和方法区,但是每个线程都有自己的栈和程序计数器。
我们大概的聊了聊线程与进程的区别,如果想详细了解两种的管理和区别,请查看百度百科的进程和线程。
下面我们就来聊聊为什么要使用线程池:我们都知道Java是没有权限区操作计算机底层的,所有Java提供了一个native
关键字,这个关键字就是Java通过其他语言来为Java提供一些Java无法做到的事情。我们在学多线程的时候使用Thread类中的start()
方法来启动多线程。而该类什么都没有做,直接是调用了一个莫名其妙的方法start0()
,而该方法就是使用的native
关键字标记的,而且没有实现方法。
public synchronized void start() {
if (threadStatus != 0) throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
既然Java无法主动调用系统底层,那么每一次创建线程,都需要调用操作系统内核的 API,操作系统要为线程分配一系列的资源,成本很高,所以线程是一个重量级的对象,应该避免频繁创建和销毁。而在一个项目的使用过程中,高并发是常有的事情,如果每个线程都要重复创建、使用、销毁的过程,那么用户体验就会很差。基于如此,我们就应该使用线程池来将线程统一管理。
线程池说白了就是多个线程的一个集合,这些线程在第一次使用的时候就会被创建,然后需要的时候就直接从池里面取出一个线程,使用完了就放回池中,省去了创建和销毁的过程,提高了程序响应速度。
创建线程池
通过ThreadPoolExecutor实现线程池的创建
在使用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;
}
线程工厂就使用Executors
中提供的defaultThreadFactory()
即可,需要注意的一点是拒绝策略ThreadPoolExecutor
类提供了四种,不同的拒绝策略代表了不同的操作方式。它们分别是:
- AbortPolicy:默认的拒绝策略,throws RejectedExecutionException
- CallerRunsPolicy:提交任务的线程自己去执行该任务
- DiscardPolicy:直接丢弃任务,不抛出任何异常
- DiscardOldestPolicy:丢弃最老的任务,加入新的任务
最后我们通过作图来看看着七个参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LHeL2X6D-1603503169697)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023184317289.png)]
接下来就是实际操作环节了。当我们的拒绝策略设置为默认的时候,当线程请求量没有达到最大时(最大线程数+队列数):
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
/**
* 参数一:核心线程数(常驻线程)
* 参数二:最大线程数
* 参数三:存活时间
* 参数四:时间单位
* 参数五:等待队列
* 参数六:线程工厂
* 参数七:拒绝策略
*/
ExecutorService executorService = new ThreadPoolExecutor(3,5,1L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 8; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
executorService.shutdown();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zppjNqDs-1603503169708)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023184459545.png)]
但是如果我们的请求量超过了最大接收值后呢?for (int i = 0; i < 10; i++) {……}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mhRiE26H-1603503169713)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023184621696.png)]
这个时候就会抛出异常,解决这个问题在于解决策略上。
使用Executors类创建线程池
Executors 类是从 JDK 1.5 开始就新增的线程池创建的静态工厂类,它就是创建线程池的。使用该类有6种创建方法,加下来,一一实现。
1、newFixedThreadPool创建定长线程池
public class NewFixedThreadPoolDemo {
public static void main(String[] args) {
//创建工作线程数为 3 的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i <6; i++) {
fixedThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() );
});
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("10毫秒后...");
//关闭线程池
fixedThreadPool.shutdown();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXm5H6H2-1603503169717)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023201649855.png)]
2. newCachedThreadPool创建可缓存的线程池
public class NewFixedThreadPoolDemo {
public static void main(String[] args) {
//创建工作线程数为 3 的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i <6; i++) {
fixedThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() );
});
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("10毫秒后...");
//关闭线程池
fixedThreadPool.shutdown();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dv2Qdov5-1603503169720)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023202021861.png)]
3. newScheduledThreadPool创建定长线程池,可执行周期性的任务。
public class NewScheduledThreadPoolDemo {
public static void main(String[] args) {
//创建定长线程池,可执行周期性的任务
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
for (int i = 0; i <3; i++) {
scheduledThreadPool.scheduleWithFixedDelay(() -> {
System.out.println(Thread.currentThread().getName());
}, 0, 3, TimeUnit.SECONDS);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("10毫秒后...");
//关闭线程池
scheduledThreadPool.shutdown();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XVdrPFZs-1603503169723)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023202227254.png)]
4. newSingleThreadExecutor创建单线程的线程池
public class NewSingleThreadExecutorDemo {
public static void main(String[] args) {
//单线程的线程池,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
for (int i = 0; i <3; i++) {
final int index = i;
singleThreadPool.execute(() -> {
if (index == 1) {
throw new RuntimeException("线程执行出现异常");
}
System.out.println(Thread.currentThread().getName());
});
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("10毫秒后...");
//关闭线程池
singleThreadPool.shutdown();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WJfnR9q5-1603503169725)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023202501956.png)]
5. newSingleThreadScheduledExecutor创建单线程可执行周期性任务的线程池
public class NewSingleThreadScheduledExecutorDemo {
public static void main(String[] args) {
//创建单线程可执行周期性任务的线程池
ScheduledExecutorService singleScheduledThreadPool = Executors.newSingleThreadScheduledExecutor();
//提交 3 个固定频率执行的任务
for (int i = 0; i <3; i++) {
//scheduleWithFixedDelay 固定的延迟时间执行任务; scheduleAtFixedRate 固定的频率执行任务
singleScheduledThreadPool.scheduleAtFixedRate(() -> {
System.out.println(Thread.currentThread().getName());
}, 0, 3, TimeUnit.SECONDS);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("10毫秒后...");
singleScheduledThreadPool.shutdown();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HphDG5W4-1603503169728)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023202731999.png)]
6. newWorkStealingPool创建任务可窃取线程池,空闲线程可以窃取其他任务队列的任务
public class NewWorkStealingPoolDemo {
public static void main(String[] args) {
//创建 4个工作线程的 任务可窃取线程池,如果不设置并行数,默认取 CPU 总核数
ExecutorService workStealingThreadPool = Executors.newWorkStealingPool(4);
for (int i = 0; i <10; i++) {
final int index = i;
workStealingThreadPool.execute(() -> {
try {
//模拟任务执行时间为 任务编号为0 1 2 的执行时间需要 3秒;其余任务200 毫秒,导致任务时间差异较大
if (index <= 2) {
Thread.sleep(1000);
} else {
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
try {
Thread.sleep(2000);//休眠 2 秒
} catch (InterruptedException e) {}
System.out.println("2秒后...");
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xLUGorlr-1603503169730)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201023202942665.png)]