第二部分:并发工具类22->Executor与线程池,如何创建正确的线程池
1.创建线程
创建对象,仅仅在jvm堆里分配一块内存
创建线程,调用操作系统内核api,操作系统为线程分配一系列资源,线程是重量级对象,应该避免频繁创建和销毁
2.线程池
线程池和一版池化资源不同,一般池化资源是acquire申请资源,release释放资源
java提供的线程池里没有申请线程和释放线程的方法
class XXXPool{
// 获取池化资源
XXX acquire() {
}
// 释放池化资源
void release(XXX x){
}
}
3.线程池是一种生产者-消费者模式
而不是普通池化资源的使用方式
线程池使用方式生产者
线程池本身是消费者
线程池设计思想
内嵌队列,以及工作线程
//简化的线程池,仅用来说明工作原理
class MyThreadPool{
//利用阻塞队列实现生产者-消费者模式
BlockingQueue<Runnable> workQueue;
//保存内部工作线程
List<WorkerThread> threads
= new ArrayList<>();
// 构造方法
MyThreadPool(int poolSize,
BlockingQueue<Runnable> workQueue){
this.workQueue = workQueue;
// 创建工作线程
for(int idx=0; idx<poolSize; idx++){
WorkerThread work = new WorkerThread();
work.start();
threads.add(work);
}
}
// 提交任务
void execute(Runnable command){
workQueue.put(command);
}
// 工作线程负责消费任务,并执行任务
class WorkerThread extends Thread{
public void run() {
//循环取任务并执行
while(true){ ①
Runnable task = workQueue.take();
task.run();
}
}
}
}
/** 下面是使用示例 **/
// 创建有界阻塞队列
BlockingQueue<Runnable> workQueue =
new LinkedBlockingQueue<>(2);
// 创建线程池
MyThreadPool pool = new MyThreadPool(
10, workQueue);
// 提交任务
pool.execute(()->{
System.out.println("hello");
});
线程池中有多少个线程,就起多少个工作线程WorkerThread
execute只是把任务提交到queue中,线程池内部维护的工作线程会消费queue中的任务并执行
4.如何用java中的线程池
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize : 最小线程数,有些项目很闲,至少保留corePoolSize坚守阵地
maximumPoolSize:线程池最大线程数
keepAliveTime & unit : 一个线程在一段时间内,没有执行任务,说明很闲。keepaliveTime 和unit定义这个一段时间参数,如果一个线程空闲了keepaliveTime & unit 这么久 ,并且线程池的线程数量大于corePoolSize,那么空闲线程就要回收
workQueue:工作队列
threadFactory:定义如何创建线程,给线程指定有意义的名字
handler:定义任务的拒绝策略,如果线程池所有线程都在忙,工作队列也满了,那么提交任务,线程池就会拒绝接收
以下4种拒绝策略:
CallerRunsPolicy:提交任务的线程自己去执行该任务。
AbortPolicy:默认拒绝策略,会throws RejectedExecutionException。
DiscardPolicy:直接丢弃任务,没有任何异常抛出。
DiscardOldestPolicy:丢弃最老的任务,把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。
5.使用线程池的注意事项
目前大厂都不建议直接使用静态工厂类Executors了
主要原因是Executors提供的很多方法默认使用的是无界队列LinkedBlockingQueue,高负载情况,无界队列很容易OOM
OOM会导致所有请求无法处理,致命问题,所以建议使用有界队列
使用有界队列,任务过多时,默认拒绝策略会throw RejectedExecutionException运行时异常,运行时异常编译器不强制catch它,开发人员很容易忽略,拒绝策略要谨慎使用。线程池处理的任务非常重要,建议自定义拒绝策略,自定义拒绝策略往往和降级策略配合使用
线程池里面提交任务,任务要自己捕获异常。