线程池的概念和工作机制
概念:首先系统空闲时在创建大量线程,这些线程的集合成为线程池。线程的生老病死都由线程池来决定。
工作机制:当有任务到来时,提交给线程池,由线程池来指定线程执行任务。线程池会在内部寻找是否有可以执行任务的线程。任务执行完成后,线程不会被销毁,而是进入空闲状态。一个线程一个时刻只能执行一个任务,但是却可以向线程池提交多个任务(只不过后来的任务可能需要等待)。
为什么要使用线程池(好处)?
降低资源的消耗:线程在创建和销毁时都是很耗费资源和时间的,我们希望通过一种机制,可以避免频繁地创建和销毁线程。
提高响应速度:因为线程池中的线程大部分是事先系统在空闲时创建的,所以当有任务到来的时候,可以直接使用已有线程,而不用去创建。任务一来就可以直接执行。
控制线程的并发数量:当线程的数目非常多时,我们就需要考虑高并发带来的一系列问题。多个线程可能因为争夺资源而使系统崩溃,运用多线程可以有效的控制线程的数目。
提高线程的可管理性:可以对某些线程设定延时执行(DelayQueue)、或者循环执行等策略。
线程池ThreadPoolExecutor
1.// 先看一条创建线程池的语句
2.ExecutorService service = Executors.newFixedThreadPool(5);
在这里需要提到几个接口和类:Executor, ExecutorService, Executors, ThreadPoolExecutor
关于这几个接口和类,这里有篇文章讲得更详细:传送门
Executor:理解为执行器,内部只要一个执行方法 execute(Runnable command)。是线程池的一个核心接口。
ExecutorService:继承了Executor,并做了拓展,增加了一堆供程序员开发用的api,所以你用起ExectorService才会那么舒服,其次,它还增加了对线程池生命周期的概念,一个线程池的声明周期有三种状态:运行、关闭和终止。
Executors:是一个用来创建线程池的工具类(像Collections类的存在),其返回的线程池都是实现了ExecutorService接口。
ThreadPoolExecutor:该类继承AbstractExecutorService抽象类,实现了ExecutorService接口,内部维护着一个线程池。一般我们只需要通过这个类的构造函数来配置线程池就好了。
ThreadPoolExecutor这是学习多线程的开头,通过学习该类的参数,来慢慢理解线程池内部的结构。
通过ThreadPoolExecutor的构造函数看参数
参数虽然多,但是确实必须理解的,而且其中有两种是不用去理会的
corePoolSize:核心线程的最大数目。
核心线程:当线程池在新建线程时,如果当前池内的线程数 < corePoolSize,那么创建出来的就是核心线程。
核心线程默认情况下会一直存在于线程池中,即使这个线程一直不做事。不过我们可以通过ThreadPoolExecutor的allowCoreThreadTimeOut属性,设置其为true,那么线程池就会去回收长时间不做事的线程了
maximumPoolSize
线程池中的最大线程数目 = 核心线程数 + 非核心线程数。
keepAliveTime
该线程池中,非核心线程的闲置时间,超时销毁。
TimeUtil util
keepAliveTime的单位,TimeUtil是一个枚举类型,包括了
NANOSECONDS:微毫秒
MICROSECONDS:微秒
MILLISECONDS:毫秒
SECONDS:秒
MINUTES:分钟
HOURS:小时
DAYS:天
BlockingQueue
任务队列,就是我们提交的任务,里面都是等待被执行的Runnable对象。
当核心线程都在忙的时候,新提交的任务就会被放在队列里等待被执行。如果队列满了,那么就开始创建非核心线程执行任务。
常见的队列类型
SynchronousQueue:这个队列拿到新的任务之后,会直接提交给线程处理,不会保留任务。如果所有的线程都在工作,那么线程池就创建一个新的线程。但是我们知道maximumPoolSize就是用来限制线程的数目的,如果超过这个值,就会报错,所以使用这种队列的时候,需要把maximumPoolSize设置为Integer.MAX_VALUE,即无限大。