文章目录
1. 线程池工作原理
2.任务阻塞队列
class BlockingQueue<T>{
private Deque<T> queue=new ArrayDeque<>();
private ReentrantLock lock=new ReentrantLock();
private Condition fullWaitSet=lock.newCondition();
private Condition emptyWaitSet=lock.newCondition();
priavte int capacity;
}
阻塞队列实现原理:
1、 成员变量:既然是队列就需要一个队列来存储任务,多个线程从队列中存取任务时需要加锁保证线程安全,需要规定队列的长度,即能够容纳的任务数量,设置两个条件变量full和empty:当队列满的时候,需要像队列加任务线程需要等待,队列空的时候需要从队列取元素的线程应该等待
2、 取任务方法:先加锁,查看队列中是否有任务,若没有,则进入empty等待队列,可以采用await(time,TimeUnit)设置等待时间,如果超过这个时间还没人唤醒,就不尝试从队列中取方法了;若有,从队列头取出任务,最后释放锁,并唤醒所有在full等待队列中等待的线程,方便其将任务入队
3、 存任务方法:先加锁,查看队列中任务是否已满,若满,则进入full等待队列,可以设置超时时间,释放锁;若未满,将任务加入队尾,最后释放锁,并唤醒所有在empty等待队列中等待的线程,方便其从队列头取任务
3.线程池
class ThreadPool{
//任务队列
private BlockingQueue<Runnable> taskQueue;
//线程集合
private HashSet<Worker> workers =new HashSet<>();
//核心线程数
private int coreSize;
//获取任务超时时间
private long timeout;
private TimeUnit timeUnit;
public ThreadPool(int coreSize,long timeout,TimeUnit timeUnit,int queueCapcity){
this.coreSize=coreSize;
this.timeout=timeout;
this.timeUnit=timeUnit;
this.taskQueue=new BlockingQueue<>(queueCapcity);
}
}
线程池是一个集合,用HashSet来实现线程池,为了方便对线程池的线程行为进行个性化编写,用worker类继承Thread,相当于把线程包装了成自己想要的。
线程池中线程的创建是懒惰式的创建,线程池先查看任务队列中是否有任务,有则取出来先保存着。然后查看如果创建一个新线程,有没有超过核心线程数的最大值,超过了就再把任务塞回任务队列,没有就先加锁,再创建worker线程,并把之前保存的线程交给worker执行。
//执行任务
public void execut(Runnable task){
if(workers.size()<coreSize){
Worker worker=new Worker(task);
workers.add(worker);
worker.start();
}else{
taskQueue.put(task);
}
}
worker会进入一个循环,循环中先执行取得的任务,执行完之后再判断阻塞队列中还有没有,有就继续取,回到循环开始执行取出的另一个任务。当没有任务在任务队列中了,就退出循环,加锁,删除线程池的这个worker线程。
@Override
public void run(){
while(task!=null||(task=taskQueue.take())!=null){
try{
log.debug("执行",task);
task.run();
}catch(Exception e){
e.printStackTrace;
}finally{
task=null;
}
}
synchronized(workers){
log.debug("worker被移除",this);
workers.remove(this);
}
}
4.拒绝策略
当线程因为队列满或者空队列而无法进行操作进入等待状态时,那么它就有好几种选择:
1、死等
2、超时等待
3、让调用者放弃任务执行
4、让调用者抛出日常
5、让调用者自己执行任务
如果把一个具体的策略写入线程池代码中,那么就不具备动态改变的优势,那么我们把选择哪种策略的决定权交给调用者:需要用到拒绝策略,当操作队列受阻,时执行调用者选择拒绝策略方法
@FunctionalInterface
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue,T task);
}
public void tryPut(RejectPolicy<T> rejectPolicy, T task){
lock.lock();
try{
//判断队列是否满
if(queue.size()==capacity){
rejectPolicy.reject(this,task);
}else{//有空闲
queue.addLast(task);
emptyWaitSet.signal();
}
}finally{
lock.unlock();
}
}
5. ThreadPoolExecutor
5.1 线程池状态
线程有状态,ThreadPollExecutor线程池也有状态,它用int的高三位来存储线程池的状态信息
一共有5种状态
RUNNING 状态: 111 线程池可以从阻塞队列中存取任务,也能执行任务
SHUTDOWN状态:000 线程池不会再往阻塞队列中存任务,但会执行现在正在执行的任务,以及把阻塞队列中任务执行完
STOP状态:001线程池会中断所有正在执行的任务,并丢弃阻塞队列的所有任务
TIDYING状态:010线程池已经完成所有任务,即将被关闭
TERMINATED状态:011终结状态
为什么把线程池状态和线程池数量存储到一个int种,因为当线程池工作时,这两个数据经常一起改变,那么存在一起就能保证两个数据一起改变的原子性。
5.2 构造方法
该类构造方法中有核心线程数和最大线程数的区别
核心线程数就和之前的解释一样,但是现在最大线程数-核心线程数=救急线程数。当所有核心线程都在执行任务,且任务队列已满,又有新的任务想加入队列时,线程池就创建一个救急线程来执行这个任务,且默认执行完该救急线程就被销毁,而核心线程一直存在并尝试从任务队列中存取数据。
在参数中有keepAliveTime和unit两个参数,都是针对救急线程的,规定救急线程没有任务可以做的时候最多生存多久,以防止一执行完就销毁,马上又来线程需要救急线程的。
拒绝策略
当所有线程都在忙,又有新的任务要加入队列时,该类就会采用拒绝策略来处理这样的状况,该类实现了四种策略:
DiscardPolicy:放弃将这个任务加入队列
DiscardOldestPolicy:将队列中时间最久的任务出列,用这个任务取而代之
CallerRunsPolicy:让调用者来执行这个任务
AbortPolicy:抛出异常(如果不设置,该策略是默认采用的策略)
5.3 固定大小线程池newFixedThreadPool
Executor executor= Executors.newFixedThreadPool(2);
查看它的构造方法:
该线程池将核心线程数和最大线程数设置为相同,代表没有救急线程,且阻塞队列参数用LinkedBlockingQueue实现,该参数代表阻塞任务队列是无界的,即可以存储无限的任务。
适用于任务量已知却相对耗时的情况
5.4 带缓冲线程池newCachedThreadPool
从构造方法可以看出,该线程池只有救急线程,且执行完任务后60s销毁。
SynchronousQueue队列是一个没有容量的队列,意思是:当有任务需要加入到队列时,就阻塞在那里等待创建好一个救急线程来取出任务进行运行(符合救急线程运行原理:不执行已经在队列中的任务)
该线程池适合在使用在任务量大且任务执行时间较短的情况
5.5 单线程线程池newSingleThreadExecutor
单线程只会创造出一个线程从无界队列中取出任务串行执行,当任务执行失败后,这个线程会结束并被销毁,为了保证线程池的运行,线程池会再创建出来一个新线程
也可以从构造方法看出FinalizableDelegataedExecutorService对线程ThreadPoolExecutor进行了包装,即不能用threadPollExecutor.function() 直接调用方法了,需要用包装类的方法来间接调用,且这个包装类的实现只暴露了Executor接口,只能实现这个接口的公共方法,未实现对被包装类的所有方法的实现,不能调用threadPoolExecutor的独特方法。
该类用在用户想要串行执行任务的场景。
5.6 提交任务方法
该线程池类提交任务只需要写一行代码,比如pool.submit(task),这行代码执行就会将这个任务加入队列,然后根据不同情况来调取线程执行这个方法
Execute方法
该方法传入参数是Runnable,没有返回值
Submit
该方法传入参数是Callable,有返回值,用Future类的对象来接收,可以用f.get()方法在主线程等待返回值
InvokeAll
通过传入由Callable实现的集合来提交一系列任务,用Future的集合来接收返回值,也可以设置超时时间
InvokeAny
通过传入由Callable实现的集合来提交一系列任务,但是只有一个返回值,该返回值是最先执行完的任务的返回值,返回后,取消其他还在执行的线程,可以设置超时时间。
5.7 停止方法
shutdown()
将线程池置为shutdown状态
shutdownNow()
把线程池置为stop状态