池化技术概述:
池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http链接池等等都是对这个思想的应用。池化技术的思想
- 主要是为了减少每次获取资源的消耗,提高对资源的利用率。
- 线程池提供了一种限制和管理资源(包括执行一个任务)
- 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
好处:
- 降低资源消耗、浪费,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要的等线程创建就能立即执行。
- 提高线程的可管理行。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行系统统一的分配,调优、监控。
一、Executor创建线程池
通过Executor并发框架的工具类创建Executors来实现我们可以创建三种类型的ThreadPoolExecutor;
CachedThreadPool
该方法返回一个可根据实际情况调整线程数量的线程池。
- 线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。
- 若所有线程池都在工作,又有新的任务提交,则会创建新的线程处理任务。
- 所有线程在当前任务执行完毕后,将返回线程池进行复用。
ScheduledThreadPoolExecutor
主要用来在给定的延迟后运行任务,或者定期执行任务。这个在实际项目中基本不会被用到,也不推荐使用,了解即可。
FixedThreadPool
该方法返回一个固定线程数量的线程池。该线程池中的数量始终不变。
- 当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。
- 若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
SingleThreadExecutor:
方法返回一个只有一个线程的线程池
- 当有一个新的任务提交时,任务会被保存在一个任务队列中。
- 待线程空闲,按先入先出的顺序执行队列中的任务。
注意:
《阿里开发手册》中强制线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更明确线程池的运行规则,规避资源耗尽的风险。
Executors弊端: - CachedThreadPool 和 ScheduledThreadPoolExecutor:允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量线程导致OOM。
- FixedThreadPool 和 SingleThreadExecutor:运行请求的队列长度为Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
二、ThreadPoolExecutor创建线程池
ThreadPoolExecutor 类中提供的四个构造方法。我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么)
ThreadPoolExcutor七大参数
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;
}
二、ThreadPoolExecutor构造函数重要参数分析
ThreadPoolExecutor3个最重要的参数:
- corePoolSize:核心线程数线程数定义了最小可以同时运行的线程数量。
- maximumPoolSize:当队列中存在任务达到队列容量的时候,当前可以同时运行的线程数量变为最大数量。
- workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会放在队列中。
其他4个参数 - keepAliveTime:当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime才会被回收销毁;
- unit:keepAliveTime参数的时间单位。
- threadFactory:executor创建新线程的时候会用到。
- handler:拒绝策略
四大核心参数
①corePoolSixe:
核心线程数线程数定义了最少可以同时运行的线程数量。
②workQueue:
当前新任务来的时候会先判断当前运行的线程数量是否达到核心线程数。达到则新的任务会放在队列中。
③maximumPoolSize:
当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
④RejectExecutionHandler:
如果当前队列也已经被放满了任务,并且运行的线程数量达到了最大线程数量,ThreadPoolTaskExecutor将才去拒绝策略。
(1)AbortPolicy【默认策略】:抛出RejectExecutionException来拒绝新任务的处理。这代表你将丢失对这个任务的处理。
(2)CallerRunsPolicy【提供可伸缩队列】:直接在调用execute方法的线程中运行(run)被拒绝的任务。
- 该策略直接在调用这线程中,运行当前的被丢弃的任务
- 调用者线程:即调用execute这个方法的线程:
调用execute方法的线程,同时也是创建当前线程池的线程
特别的当前调用者是主线程的时候,指的事main线程
(3)DiscardPolicy:直接拒绝任务,不抛出错误也不会给你任何的通知。
(4)DiscardOldestPolicy:只要还有任务新增,直接会丢弃阻塞队列workQueue的最老的任务,并将新的任务加入。
eg:
另外3个参数
⑤keepAliveTime:
当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过keepAliveTime才会被回收销毁。
⑥nuit:
keepAliveTime参数的时间单位。
⑦threadFactory:
executor创建新线程的时候会用到。
三、线程池execute()和submit()的区别
1.execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
2.submit()方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功,并且可以通过Future的get()方法来获取返回值
a.get()方法会阻塞当前线程直到任务完成,
b.get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候又可能任务没有执行完。
DEMO
package threads.juc;
import java.util.Date;
/**
* @author :suguohu
* @date :Created in 2023/3/16 15:46
* @description :
* @modified By:suguohu
* @version: 1.0.0
*/
public class MyRunnable implements Runnable{
private String command;
public MyRunnable(String s){
this.command = s;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + new Date());
System.out.println(Thread.currentThread().getName() + ":" + new Date());
}
private void processCommand(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "MyRunnable{" +
"command='" + command + '\'' +
'}';
}
}
package threads.juc;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author :suguohu
* @date :Created in 2023/3/16 15:51
* @description :
* @modified By:suguohu
* @version: 1.0.0
*/
public class ThreadPoolExecutorDemo {
private static final Integer CORE_POOL_SIZE = 5;
private static final Integer MAX_POOL_SIZE = 10;
private static final Integer QUEUE_CAPACITY = 100;
private static final Long KEEP_ALIVE_TIME = 1L;
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy()
);
for (int i = 0; i < 1000; i++) {
Runnable worker = new MyRunnable("线程" + i);
// 执行Runnable
poolExecutor.execute(worker);
}
// 终止线程池
poolExecutor.shutdown();
while (!poolExecutor.isTerminated()) {
//System.out.println("线程未完成");
}
System.out.println("线程结束!");
}
}