1)、线程池
(1)、为什么需要使用线程池
降低资源的消耗,提高线程利用率,降低创建和销毁线程的消耗;
提高响应速度,任务来了,直接有线程可用可执行,而不是先创建线程,再执行;
提高线程的可管理性,线程是稀缺资源,使用线程池可以统一分配调优监控;
(2)、线程池的核心参数
public ThreadPoolExecutor(int corePoolSize,//核心线程数,线程池创建好以后就准备就绪的线程数量,就等待来接受异步任务去执行。这些线程创建后并不会消除,而是一种常驻线程。
int maximumPoolSize, //最大线程数量,控制资源并发。它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足需求时,此时就会创建新的线程,但是线程池内线程总数不会超过最大线程数。
long keepAliveTime,//存活时间,表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分线程如果空闲一定的时间则会被消除,我们可以通过setKeepAliveTime来设置空闲时间。
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//阻塞队列,如果任务有很多,就会将目前多的任务放在队列里面,只要有线程空闲,就会去队列里面取出新的任务继续执行。假设我们现在核心线程已经被使用,还有任务进来则全部放入队列,直到整个队列被放满但任务还在继续进入则会开始创建新的线程
ThreadFactory threadFactory, //线程的创建工厂
RejectedExecutionHandler handler //如果队列满了,按照我们指定的策略,拒绝执行任务)
(3)、线程池的工作流程
当创建了线程池以后,等待提交过来的任务请求;当调用execute()方法添加一个请求任务时,线程池会做如下判断:
①如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
②如果正在運行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
③如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻执行这个任务;
④如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
当一个线程完成任务时,它会从队列中取下一个任务来执行;
当一个线程无事可做超过一定的时间(keepAliveTime),线程池会判断如果当前运行的线程池是否大于corePoolSize,如果大于,那么这个线程就会被停掉。
(4)、线程池的状态
Running,在Running状态下,线程池可以接收新的任务和执行已添加的任务。线程池的初始化状态是Running。换句话是,线程池一旦被创建就处于Running状态,并且线程池中的任务数为0!线程池处在Running状态时,能够接收新任务以及对已添加的任务进行处理。
shutdown,线程池处在shutdown状态时,不接收新任务,但能处理已添加的任务。当一个线程池调用shutdown()方法时,线程池由running->shutdown。
stop,线程池处在stop状态时,不接收新任务,不处理已添加的任务,并且会中断正在执行的任务。调用线程池的shutdownNow()方法的时候,线程池由(running或者shutdown)->stop。
tidying,当所有的任务已终止,记录的任务数量为0,线程池会变为tidying状态。当线程池变为tidying状态时,会执行钩子函数terminated()。terminated()方法在ThreadPoolExecutor类中是空的,没有任何实现。
terminated,当钩子函数terminated()被执行完成之后,线程池彻底终止,就变成terminated状态。线程池处在tidying状态时,执行完terminated()之后,就会由tidying->terminated。
(5)、线程池中的拒绝策略
AbortPolicy(默认):直接抛出RejectedException异常阻止系统正常运行
CallerRunPolicy:调用者运行一种调节机制,该策略既不会抛弃任务,也不会抛出异常
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交
DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常,如果允许丢失,这是最好的拒绝策略。
(6)、线程池有哪些队列
ArrayBlockingQueue:基于数组结构的有界阻塞队列,按先进先出对元素进行排序;
LinkedBlockQueue:基于链表结构的有界/无界阻塞队列,按先进先出对元素进行排序,吞吐量通常高于ArrayBlockQueue,Executors.newFixedThreadPool试用了该队列。
SynchronousQueue:不是一个真正的队列,而是一种在线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接受这个元素。如果没有线程等待,并且线程线程池的当前大小小于最大值,那么线程池将创建一个线程,否则根据拒绝策略,这个任务将被拒绝。使用直接移交将更高效,因为任务会直接移交给执行它的线程,而不是放在队列中,然后由工作线程从队列中提取任务。只有当线程是无界或者可以拒绝任务时,该队列才有实际价值。Executors.newCacahedThreadPool使用了该队列。
PriorityBlockingQueue:具有优先级的无界队列,按优先级对元素进行排序。元素的优先级是通过自然排序或Comparator来定义的。
(7)、为什么要使用阻塞队列而不是普通队列
一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲的长度,就无法保留当前的任务了,阻塞队列可以通过阻塞可以保留住当前想要继续入队的任务。
阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入了wait状态,释放了cpu资源。
阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池李永刚阻塞队列的take方法挂起,从而保持线程的存活、不至于一直占用CPU资源。
(8)、线程池中线程复用的原理
线程池将线程和任务进行解耦,线程就是线程,任务就是任务,摆脱了之前通过Thread创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理是在线程池对Thread进行封装,并不是每次执行都会调用Thread.start()来创建线程,而是让每个线程去执行一个循环任务,在这个循坏任务中不停检查是否还有任务需要执行,如果有则执行,也就是调用任务中的run方法,将run方法当一个普通方法执行,通过这种方式只使用固定的线程就将所有任务的run方法串联起来。
(9)、Executors提供了哪些创建线程池的方法
newFixedThreadPool:固定线程数的线程池。corePoolSize=maximumPoolSize,keepAliveTime=0,工作队列使用无界的LinkedBlockingQueue。适用于为了满足资源管理的需求,而需要限制当前线程数量的场景,适用于负载比较重的服务器。
newSingleThreadExecutor:只有一个线程的线程池。corePoolSize=maximumPoolSize=1,keepAliveTime为0,工作队列使用无界LinkedBlockingQueue。适用于需要保证顺序的执行各个任务的场景。
newCachedThreadPool:按需要创建新线程的线程池。核心线程数为0,最大线程数为Integer.MAX_VALUE,keepAliveTime为60秒,工作队列使用同步移交SynchronousQueue。该线程池可以无限扩展,当需求增加时,可以添加新的线程,而当需求降低时会自动回收空闲线程。适用于执行很多的短期异步任务,或者负载较轻的服务器。
newScheduledThreadPool:创建一个以延迟或定时的方式来执行任务的线程池,工作队列DelayedWorkQueue。适用于需要对个后台线程执行周期任务。
newWorkStealingPool:jdk1.8新增,用于创建一个可以窃取的线程池,底层使用ForkJoinPool实现。