1.线程池
1.1线程池是什么
线程池是一种利用池化技术思想来实现的线程管理技术,主要是为了复用线程、便利地管理线程和任务、并将线程的创建和任务的执行解耦开来。我们可以创建线程池来复用已经创建的线程来降低频繁创建和销毁线程所带来的资源消耗。**线程池可以设置一定数量的空闲线程,这些线程即使没有任务时仍然不会释放.线程池也可以设置最大线程数,防止任务过多压垮服务器.**在JAVA中主要是使用ThreadPoolExecutor
类来创建线程池,并且JDK中也提供了Executors工厂类来创建线程池
线程池还有以下优点:
- 降低资源消耗,复用已创建线程可以降低创建\销毁线程的消耗.
- 提高响应速度.使用线程池,在任务需要处理是可以不等待线程的创建立即执行.
- 提高线程的可管理性.使用线程池能统一的分配,调优和监控.
总而言之,线程池最大的好处就是减少每次启动\销毁线程的损耗.
1.2为什么需要线程池
若是没有线程池,创建线程执行任务就如下图所示:
可以看到,每执行一个任务都要创建新的线程,一直这样创建线程会产生很大的开销,且无法统一的管理每个创建的线程.
1.3标准库中的线程池
1.3.1 Executors
使用Executors.newFixedThreadPool(10)
能创建出固定包含10个线程的线程池,其返回值类型为ExecutorService
,通过ExecutorService.submit
可以注册一个任务到线程池中.
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
});
Executors创建线程池的几种方式:
newFixedThreadPool
:创建固定线程数的线程池newCachedThreadPool
:创建线程数目动态增长的线程池.newSingleThreadExecutor
:创建只包含单个线程的线程池.newScheduledThreadPool
:设定延迟时间后执行命令,或者定期执行命令,是进阶版的Timer.
Executors
本质上是ThreadPoolExecutor
类的封装
1.3.2 ThreadPoolExecutor
ThreadPoolExecutor
是应用最广的低层线程池类,它实现了Executor
和ExecutorService
接口.以下是ThreadPoolExecutor
的构造方法和几个最常用的方法:
public class ThreadPoolExecutor extends AbstractExecutorService {
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory treadFactory,RejectedExecutionHandler handler) {}
@Override
public void execute(Runnable command) {}
@Override
public void shutdown() {}
@Override
public List<Runnable> shutdownNow() {}
@Override
public boolean isShutdown() {}
@Override
public boolean isTerminated() {}
@Override
public boolean awaitTermination(long timeout,TimeUnit unit) throw InterruptedException {}
...
}
[解释]:
corePoolSize
:核心线程数maximumPoolSize
:最大线程数keepAliveTime
:如果池中当前有超过 corePoolSize 个线程,则多余的线程如果空闲时间超过keepAliveTime
(请参阅getKeepAliveTime(TimeUnit)
)将被终止TimeUnit
:允许空闲的时间.keepaliveTime
的时间单位是时分秒以及其他值workQueue
:传递任务的阻塞队列threadFactory
:执行程序创建新线程时使用的工厂handler
:由于达到线程边界和队列容量而阻塞执行时使用的处理程序.有四种预定义的处理程序策略:AbortPolicy()
:处理程序在决绝是抛出运行时RejectedExecutionException
,此时所有任务全部停止CallerRunsPolicy()
😗*调用execute
的线程本身运行任务.**这提供了一种简单的反馈机制,可以减慢提交新任务的速度. 可以简单理解为谁给的任务谁解决.DiscardOldestPolicy()
:如果执行器未关闭,工作队列头部的任务将被丢弃,然后重试执行(可能会再次失败导致重复).这种策略很少被接受.在几乎所有情况下,还应该取消在任何等待其完成的组件中导致异常的任务,并且/或者记录失败信息,如ThreadPoolExecutor.DiscardOldestPolicy
文档中所示.因为在队列中,头部的任务是队列中最老的任务,所以换句话说处理方式就是丢弃队列中最老的任务.DiscardPolicy()
:无法执行的任务会被简单地丢弃。此策略仅适用于从不依赖任务完成的极少数情况。此处可以简单理解为把新来的任务丢弃.
关于keep Alive Time的设置
以上内容是Java API文档原文解释与部分个人理解,下面我举个例子以便理解:
corePoolSize
就像公司里的正式员工,MaximumPoolSize
则是正式员工+实习生.当工作任务多的时候就会招聘实习生来缓解压力,任务少的时候则把实习生裁掉减小开销.但是呢什么时候任务多什么时候任务少是不确定的,因此需要留个观察期KeepAliveTime
.如果在keepAliveTime
内没有什么繁琐的任务那大概后面也暂时不会有,所以超过keepAliveTime
就把实习生裁掉,而TimeUnit就是用来设置允许实习生“摸鱼”(keepAliveTime
)的时间.希望我这样说能对各位有帮助.
1.3.3 线程池的工作流程
关于最大线程数MaximumPoolSize
的达到条件:假设核心线程数corePoolSize
为3,最大线程数MaximumPoolSize
为8,keepAliveTime为3秒.随着任务数量上升,线程池会不断创建线程直到达到核心线程数corePoolSize
3,就不创建线程了.此时多余的任务通过加入阻塞队列BlockingQueue
来运行,当超过阻塞队列+核心线程数时,线程池不得不扩大线程个数来满足当前任务的运行,这是就需要创建新的线程,直到数量达到8(最大线程数).
ThreadPoolExecutor
里的方法还有很多,这里只是介绍了以下构造方法,剩下的内容可以查看[Java API文档](Java17中文文档 - API参考文档 - 全栈行动派 (qzxdp.cn)).个人建议是英文文档和翻译成中文的 文档对照着看,当中文文档看着很怪时可以去看看原文是怎么说的.