多线程之线程池(上)
前言
最近一段时间,我们一直都在分享多线程相关知识,也一直有用线程池,但是一直没有介绍线程池相关知识,所以今天我们就先来看下线程池相关的知识点。
线程池
ThreadPool
,线程池,顾名思义就是存放线程的池子,也是jdk1.5
引入的。对我们而言,它的最主要优势就是简化了线程启动流程,让我们可以更方便地使用多线程,再也不用手动start
线程,直接通过线程池提交我们的任务即可,而且合理使用线程池至于可以带来以下几个好处:
降低资源消耗:复用线程,降低创建和销毁线程带来的资源消耗
提高响应速度:使用线程池,省去了线程创建和初始化过程,所以任务可以更快执行
提高线程的可管理性:可以直接通过线程池管理、监控、调度线程,线程管理更方便
常用线程池
常用的线程池有SingleThreadExecutor
、CachedThreadPool
、ScheduledThreadPool
、FixedThreadPool
,他们分别是单线程调度器,缓存线程池,定时任务线程池和固定线程池,他们都可以通过Executors
创建,调用对应的静态方法即可,由于这一块的内容比较多,所以今天就简单提一下,后面专门讲一次。
自定义线程池
我们今天着重讲下自定义线程池,自定义线程池也很简单,直接new ThreadPoolExecutor()
,然后传入对应的参数即可,大家可以看下下面的示例:
int corePoolSize = 10;
int maximumPoolSize = 20;
long keepAliveTime = 1000;
TimeUnit unit = TimeUnit.MICROSECONDS;
BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
for (int i = 0; i < 50; i++) {
threadPoolExecutor.execute(() -> {
String name = Thread.currentThread().getName();
System.out.println("hello threadPool: "+ name);
});
}
threadPoolExecutor.shutdown();
ThreadPoolExecutor
有三个构造方法,至少需要三个参数
其中第一个参数是线程池的基本大小,当你提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使有空闲的线程存在,线程池依然会启动一个新线程来执行当前任务,直到线程池中的线程数达到线程基本大小(corePoolSize
);
第二个参数是线程池允许创建的最大线程数。如果工作队列(第三个参数)满了,且创建线程数已达到线程池基本大小,则线程池会继续创建新的线程来执行任务。如果你指定的工作队列是无界的,那这个参数也就失效了。
第三个参数就是线程池工作队列,就是当你需要执行的任务超过线程池基本大小的时候,会把超出部分放进工作队列,等待线程池基本线程资源释放。
下面我们分别验证以上三点,运行上面的示例代码:
在第一次循环的时候(i=0
),我们发现线程池的size
是0
,活动线程数也是0
,任务队列也是0
,完成任务数也是0
,这也说明线程池在最开始的时候是没有创建线程的;
然后我们让他循环到第9
次(i=8
),这时候线程池已经被初始化,有8
个线程(由于断点的原因,第9
个线程尚未被创建),活动线程数5
,完成执行的任务数3
,任务队列还是0
,说明确实在未达到线程池基本大小时,会不断创建新的线程;
我们继续执行,让他循环到第15
次(i=14
),可结果似乎和我们预期不一样,按照预期,线程池的size
应该是10
,活动线程数也是10
,任务队列也是4
,完成任务数可能不确定,所以这里肯定不能通过debug
的方式来看了,因为debug
停顿之后好多线程资源已经被释放,任务根本就不会堆积,所以任务队列就不会有数据:
所以这里我在线程池启动任务前加一行打印,打印线程池数据
然后在运行,就可以拿到运行数据:
这样的数据才是真实的,因为实际运行的时候,线程启动是非常快的,所以执行完成的任务数应该是0
,等待任务数是4
。
我们前面设定的最大线程数是20
,但是翻看运行记录,我发现线程池的大小始终是10
,说明只要不打到任务队列的上限,并不会创建新的线程,这里我们把循环次数改为60
,然后运行下:
但是依然没有创建新的线程,因为还是没有达到任务队列上限,我们把循环次数再调大一点,调到70
:
现在线程池的大小就变成了19
,活动线程数19
,但是这时候如果你继续调大循环次数,线程池就会报错了:
这个错误的原因就是线程池资源已经耗尽了,无法再接收新的任务了,这也就是说线程池能够处理的最大任务数是corePoolSize + maximumPoolSize + workQueue.size()
,当然,如果你的workQueue
不设定大小,那永远都不会报这个错误,当然maximumPoolSize
也就无效了。
总结
原本打算线程池一次分享完的,但是实际分享过程中发现内容太多了(已经一千五百字了),所以今天就先到这里,明天再继续分享线程池其他内容。总的来说,今天的内容已经说明白了线程池很多基础的知识点(反正我自己觉得我都有好多收获),算是干货满满吧,你如果掌握了这些知识点,至少在使用线程池的过程中会少踩好多坑。好了,今天就先说这么多吧!
- END -