线程池连环炮
一、四种常见的线程池
1、CachedThreadPool:可缓存的线程池
创建方法:
ExecutorService mCachedThreadPool = Executors.newCachedThreadPool();
1.这种线程池内部没有核心线程,非核心线程的数量为Integer.max_value,线程的数量是有没限制的。
2.在创建任务时,若有空闲的线程时则复用空闲的线程,若没有则新建线程。
3.没有工作的线程(闲置状态)在超过了60S还不做事,就会销毁。
2、FixedThreadPool:定长的线程池
创建方法:
//nThreads => 最大线程数即maximumPoolSize
ExecutorService mFixedThreadPool= Executors.newFixedThreadPool(int nThreads);
1、该线程池的最大线程数等于核心线程数,所以在默认情况下,该线程池的线程不会因为闲置状态超时而被销毁。
2、如果当前线程数小于核心线程数,并且也有闲置线程的时候提交了任务,这时也不会去复用之前的闲置线程,会创建新的线程去执行任务。如果当前执行任务数大于了核心线程数,大于的部分就会进入队列等待。等着有闲置的线程来执行这个任务。
3、SingleThreadPool:一个单线程化的线程池
创建方法:
ExecutorService mSingleThreadPool = Executors.newSingleThreadPool();
1、有且仅有一个工作线程执行任务
2、所有任务按照指定顺序执行,即遵循队列的入队出队规则
4、SecudleThreadPool:周期性执行任务的线程池
创建方法:
//nThreads => 最大线程数即maximumPoolSize
ExecutorService mScheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
1、不仅设置了核心线程数,最大线程数也是Integer.MAX_VALUE。
2、这个线程池是唯一个有延迟执行和周期执行任务的线程池。
二、线程池的工作原理
一般来说(以FixedThreadPool举例):
如果我设置的核心线程数的大小为10
一个任务进入以后,发现只有3个核心线程,则新建一个核心线程执行任务
如果有10个核心线程,则加入队列,待那个核心线程空闲以后,获取任务执行
如果队列也满了,看看线程池满了没,没满创建非核心线程执行
如果线程池也满了了,调用拒绝策略拒绝策略
如下图:
三、参数解读
int corePoolSize:该线程池中核心线程数最大值
核心线程:线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。
int maximumPoolSize: 该线程池中线程总数最大值
线程总数 = 核心线程数 + 非核心线程数。
long keepAliveTime:该线程池中非核心线程闲置超时时长
一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉,如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。
queue:
选择的队列
BlockingQueue workQueue:
该线程池中的任务队列:维护着等待执行的Runnable对象当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。
常用的workQueue类型:
SynchronousQueue:
这个队列接收到任务的时候,会直接提交给线程处理,而不保留它如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
LinkedBlockingQueue:
这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
ArrayBlockingQueue:
可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
DelayQueue:
队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
TimeUnit unit:keepAliveTime的单位
TimeUnit是一个枚举类型,其包括:
NANOSECONDS : 1微毫秒 = 1微秒 / 1000
MICROSECONDS : 1微秒 = 1毫秒 / 1000
MILLISECONDS : 1毫秒 = 1秒 /1000
SECONDS : 秒
MINUTES : 分
HOURS : 小时
DAYS : 天
ThreadFactory threadFactory:
创建线程的方式,这是一个接口,你new他的时候需要实现他的Thread newThread(Runnable r)方法,一般用不上。
RejectedExecutionHandler handler:
出现异常时的拒绝策略。
有四种:
第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满
第二种DisCardPolicy:不执行新任务,也不抛出异常
第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行
第四种CallerRunsPolicy:直接调用execute来执行当前任务
四、面试题
如果在线程池中使用LinkedBlockingQueue(无界阻塞队列)会有什么问题?
如果线程处理速度慢,队列会变得特别长,可能会内存飙升或内存溢出
线程池队列满了会有什么问题?
1、如果规定了线程池大小,接下来线创建核心线程,线程池满了。会走异常处理
2、如果没规定线程池大小,会一直创建非核心线程,如果线程创建的足够多,一个线程一个栈,会导致栈内存溢出,如果内存没有一处,也会导致CPU过载,可能会导致服务器宕机
上述两个问题,解决办法,在线程居多的时候,可以使用自定义拒绝策略持久化到磁盘中,等线程数量下来以后读取执行。
如果线上机器突然宕机,线程池的阻塞队列怎么办?
必然会导致线程池里积压的任务丢失
怎么解决?
还是持久化到数据库中。任务进入队列时,将其信息插入数据库,更新状态为未提交。当任务提交到线程执行完毕后,更新为已提交。宕机后重启,高一个后台线程,扫描未提交的任务,提取信息,重新执行。