线程池:
本质上是一个对象池, 用来管理线程资源.
在任务执行前, 需要从线程池中拿出线程来执行.
在任务执行完成之后, 需要把线程放回线程池.
线程池好处:
降低资源的消耗, 线程本身是一种资源, 创建和销毁都会消耗CPU内存, 频繁的创建和销毁会浪内存.
提高任务执行的响应速度, 任务执行时, 可以不必等线程创建完成之后在执行,可以直接获取线程执行任务.
提高线程的可管理性, 线程不能无限制的创建, 需要进行统一的分配, 调优和监控.
不适用线程池坏处:
频繁的线程创建和销毁会占用更多的CPU和内积.
频繁的创建和销毁会对gc产生比较大的压力.
线程太多,线程切换带来的开销将不可忽视.
线程太少, 多核CPU得不到充分利用, 浪费资源.
线程池实现原理:
分为三部分: 核心线程池 线程池 队列 拒绝策略
主要流程:
1.判断核心线程池是否已满, 如果不是, 则创建线程执行任务
2.如果核心线程池满了, 判断队列是否满了, 如果队列没满, 将任务放在队列中
3.如果队列满了, 则判断线程池是否已满, 如果没满, 创建线程执行任务
4.如果线程池满了, 按照拒绝策略对任务进行处理
JDK中提供了一个线程池工厂😗* Executors ,很多工厂方法, 可以创建多种线程池
1.单一线程池
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
该线程池只有一个线程, 若多个任务被提交到此线程池, 那么会被缓冲到队列,当线程空闲时,按照FIFO的方式进行处理.
2.固定数量线程池
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
和创建单一线程池类似, 不同的是线程池中有多个线程, 可以并行处理任务, 若多个线程任务被提交到此线程池, 会有以下执行过程:
如果线程的数量未达到指定数量, 则创建新线程执行任务.
如果线程池的数量达到了指定数量, 而且此时有线程是空闲的, 则取出空闲线程来执行任务.
如果没有线程是空闲的, 则将任务缓冲到队列, 当线程空闲的时候, 按照FIFO的方式进行处理.
FIFO: 先进先出
3.带缓冲的线程池
该线程池中, 核心线程池长度为0, 线程池最大长度为Integer.MAX_VALUE.以SynchronousQueue作为等待队列, 从而每次往队列中插入一个元素, 必须等待另一个线程从这个队列删除一个元素.
定时调度
4.定时调度的线程池
ExecutorService newCachedThreadPool = Executors.newScheduledThreadPool();
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
ExecutorService newWorkStealingPool = Executors.newWorkStealingPool()
线程池家族
线程池的顶层接口是Executor, 这个接口定义了一个核心方法executor(Runnable command), 这个方法最后被ThreadPoolExecutor类实现, 这个方法用来传入任务, 并且该类是线程池的核心类, 构造方法如下 :
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueu, ThreadFactory threadFactory,RejectedExecutionHandler handler);
参数意义:
CorePoolSize: 核心线程池大小, 如果核心线程池有空闲的位置, 新的任务就会被核心线程池新建一个线程执行, 执行完毕不会销毁线程, 线程会进入缓冲队列等待再次被运行
MaximunPoolSize: 线程池能创建最大的线程数量, 如果核心线程池和缓冲队列都已经满了, 新的任务就会进来创建新的线程来执行, 但是数量不能超过maximunPoolSize, 否则采取拒绝接受任务策略
KeepAliveTime: 非核心线程能够空闲的最长时间, 超过时间, 线程终止, 这个参数默认只有在线程数量超过核心线程池大小时, 才会起作用.
Unit: 时间单位, 和keepAliveTime配合使用.
WorkQueue: 缓冲队列, 用来存放等待被执行的任务
ThreadFactory: 线程工厂, 用来创建线程, 一般有三个选择
ArrayBlockingQueue LinkedBlockingQueue SynchronousQueue
Handler :拒绝处理策略, 线程数量大于最大线程数量就会拒绝处理策略
四种拒绝策略为:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
Executor接口有一个子接口ExecutorService, ExecutorService的实现类AbstracExecutorService, 而ThreadPoolExecutor正是AbstracExecutorService的子类.
ThreadPoolExecutor还有两个常用的方法shutdown和submit,两者都用来关闭线程池, 但是submit有一个结果返回.
线程池任务执行
当执行executor(Runnable command)方法后, 传入一个任务,
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command);
}
}
(传入的对象实现了Runnable接口, 这就解释了: 为什么实现多线程的方式中继承Thread类不能使用线程池了)
执行过程:
首先判断任务是否为空, 为空抛出空指针异常, 否则执行下一个判断: 当前线程数量是否小于核心线程池线程数量, 是,则执行addIfUbderCorePollSize(command)方法, 在核心线程池中创建新的线程, 并且执行这个任务
总结:
如果当前线程池中的数目小于corePollSize, 则每来一个任务, 就会创建一个新的线程去执行这个任务
如果当前线程池中的线程数目>=corePollSize, 则每来一个任务, 会尝试将其添加到缓冲队列中, 如果添加成功, 则该任务会等待空闲线程将其取出去执行, 如果添加失败(一般是以为任务队列已经满了), 则会尝试创建新的线程去执行这个任务.
如果当前线程池中的线程数目达到maximunPoolSize, 则会采取任务拒绝策略进行处理.
如果线程池中的线程数量大于corePoolSize时, 如果某线程空闲时间超过keepAliveTime, 线程将被终止, 直至线程池中的线程数目小于等于corePoolSize; 如果允许核心池中的线程设置存活时间, 那么核心池中的线程空闲时间超过keepAliveTime, 线程也会被终止.