为什么使用线程池?
Thread是一个稀缺而昂贵的资源,会大量的消耗CPU,消耗内存,如果线程开启的过多,对操作系统是一个负担。线程分为创建,任务执行,销毁,使用线程池可以节省资源,缩短任务的执行时间。
线程池的使用
/**
*类说明:线程池的使用
*/
public class UseThreadPool {
//工作线程
static class Worker implements Runnable
{
private String taskName;
private Random r = new Random();
public Worker(String taskName){
this.taskName = taskName;
}
public String getName() {
return taskName;
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName()
+" Worker Runnable执行 the task : " + taskName);
SleepTools.ms(r.nextInt(100)*5);
}
}
static class CallWorker implements Callable<String>{
private String taskName;
private Random r = new Random();
public CallWorker(String taskName){
this.taskName = taskName;
}
public String getName() {
return taskName;
}
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()
+" CallWorker CallAble 执行 the task : " + taskName);
return Thread.currentThread().getName()+":"+r.nextInt(100)*5;
}
}
public static void main(String[] args)
throws InterruptedException, ExecutionException
{
ExecutorService pool = new ThreadPoolExecutor(2,4,3,TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(10),
new ThreadPoolExecutor.DiscardOldestPolicy());
for(int i=0;i<6;i++) {
Worker worker = new Worker("worker_"+i);
pool.execute(worker);
}
for(int i=0;i<6;i++) {
CallWorker callWorker = new CallWorker("callWorker_"+i);
Future<String> result = pool.submit(callWorker);
System.out.println(result.get());
}
pool.shutdown();
}
}
代码可以看到
pool.execute(worker);//实现的接口是Runnable、
pool.submit(callWorker)//实现的接口是Callable,submit会返回一个结果,要返回结果,用submit
线程池得关闭:两种方式
shutdown:尝试关闭线程池,然后把当前没有执行任务的线程进行中断,一定成功
shutdownNow:不管当前任务有没有执行,都会执行中断,不一定成功,因为线程的中断是一种协作机制,得看具体任务。
源码分析
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize:核心线程数
maximumPoolSize:当前线程池可以使用的最大线程数
keepAliveTime:有的线程空闲,控制空闲线程的存活时间,超过这个时间进行销毁
TimeUnit:存活的时间单位
workQueue:提交的任务数大于线程数,放到阻塞队列里
threadFactory: 创建线程后,适当的做位调整工作
RejectedExecutionHandler: 任务太多,队列都放不下 ,进行拒绝策略
线程池的工作机制
按上图所示,对线程池的工作原理总结
1、主线程将任务下发后,先放到corePool,核心线程里
2、继续下发任务,当核心线程满了之后,放到阻塞队列里BlockQueue里
3、主线程继续下发任务,当阻塞队列满了之后,放到最大线程数里,MaximumPool
4、当核心线程,阻塞队列,最大线程都满了之后,主线程再下发任务,就去调用拒绝策略
四种拒绝策略:
DiscardOldestPolicy:直接丢弃最老的AbortPolicy:直接抛出异常,默认配置这个
CallerRunsPolicy:让调用者线程去执行,谁往线程池提交任务,谁来执行这个任务
DiscardPolicy:最新提交的任务直接扔掉 自定义策略:实现
RejectedExecutionHandler,自定义再提交到线程池里
如何合理配置线程池,合理配置参数?
1、线程的大小 coolPoolSize ,maximumPoolSize,对线程的大小进行合理分配?
根据任务特性
Runtime.getRuntime().availableProcessors();//从操作系统去获取当前可用的CPU核心数
A、cpu密集型任务(cpu和内存频繁互动)。计算类型的操作,逻辑判断
针对CPU密集型任务,应该尽可能配置少的线程的线程池,如配置cpu+1个线程的线程池。
B、io密集型(频繁读写磁盘)任务和混合型任务。 网络通讯,读写磁盘,文件处理
针对IO密集型的任务,因为它不可能一直在执行IO操作,所以配置尽可能多的线程的线程池,如2CPU数量线程的线程池。
C、优先级不同的任务(高中低)。
混合型任务需要进行拆分,拆分为CPU密集型和IO密集型,如果这两个任务执行时间相差不大,拆分成两个线程池执行任务,如果两个任务的执行时间相差过大,则没必要进行拆分
2、阻塞队列的大小进行合理分配?
A、根据执行时间不同的任务(长中短)
执行时间不同的任务交给不同规模的线程池去执行,或者交给优先级队列去执行,让执行时间短的任务先执行。
B、是否依赖外部资源(如数据库链接)
优先级不同的任务交给优先级队列去执行,让优先级高的任务先执行
Attention: 如何区分是IO密集型还是CPU密集型? CPU主要用于计算,加密解密,逻辑类都是消耗CPU。 IO主要是文件的操作。网络数据的读写等。 网络通讯框架大部分使用的都是CPU核心数*2,根据不同的场景自己进行判断处理
拓展习题:
核心线程数为3,最大线程数为6,队列为10,这时候有1-20个任务,问执行任务的顺序是什么?
解答:插入顺序: 先核心线程数1-3,再队列 4-13,再最大线程数14,15,16.。满了之后 17 18 19 20 舍弃。
执行时候,先执行核心线程数 1,2 ,3 再执行最大线程数 14 15 16,最后执行队列 4 -13
故顺序为 1,2,3 ,14,15,16, 4,5,6,7,8,9,10,11,12,13 .
17 18 19 20 舍弃