最好用并发工具-线程池


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状态

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值