一 什么是线程池
简单理解就是装有线程的池子,和数据库连接池的概念是一样的.事先准备好一资源,当有人需要用的时候,直接过来资源,用完之后将资源归还,通过线程池可以达到多个线程的复用
程序的执行肯定会消耗占用系统的资源,我们可以通过池化技术(线程池)来优化资源的使用,使线程复用,这样可以大大降低资源的消耗,提高系统响应速度,更方便我们管理线程,
二 线程池的使用
我们可以通过ThreadPoolExecutor来创建一个线程池:
我们来分析一下源码
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(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)。
- BlockingQueue workQueue,
- threadFactory:创建线程的工厂,一般用默认即可
- handler:拒绝策略,当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常。
四种拒绝策略
-
第一种拒绝策略 new ThreadPoolExecutor.AbortPolicy(),也是默认的拒绝策略,如果还有任务需要继续执行,会抛出异常,new ThreadPoolExecutor.AbortPolicy()如果这个类创建了最大线程数,也就是线程池可以承载的是最大的线程数了,会正常执行,并且阻塞队列中的任务已经存储满了,接下来如过在假如一个线程就会
抛出异常
,java.util.concurrent.RejectedExecutionException -
第二种拒绝策略 new ThreadPoolExecutor.CallerRunsPolicy() ,这种拒绝策略是哪个线程提交的任务,哪个线程去执行,
哪里来哪里去
,也就是main线程去执行 -
第三种拒绝策略,new ThreadPoolExecutor.DiscardPolicy(), 如果队列满了
不会抛出异常
,直接
丢掉任务
, -
第四种拒绝策略,new ThreadPoolExecutor.DiscardOldestPolicy(),有新的任务需要执行时,会尝试
和最早的竞争线程资源
,也不会抛出异常.
手动创建一个线程池
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 0; i < 9; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " OK");
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}
}
线程池的处理流程
- 当使用者提交任务之后,
- 首先会判断核心线程池数量是否已满,核心线程数没满则创建线程执行任务,如果核心线程数已满,会进入第二部阻塞队列进行判断
- 阻塞队列没有满,则进入阻塞队列等待 如果阻塞队列已满,则进入第三步最大线程数判断
- 如果线程数量未达到最大线程数,则创建新的线程执行任务,如果达到最大线程数,按照拒绝策略处理无法执行的任务
如何合理的配置线程池
要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。
任务的性质:CPU密集型任务、IO密集型任务
任务的优先级:高、中和低。
任务的依赖性:是否依赖其他系统资源,如数据库连接
- CPU密集型任务尽可能配置小的线程,几核CPU就是最大线程就是几,可以保持CPU的效率最高 Runtime.getRuntime().availableProcessors() 方法获得当前设备的CPU个数。
- IO密集型 这类人物特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度),对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度,例如程序有15个大型IO任务, 十分消耗资源, 可以将最大的线程数设置为2倍,
- 优先级别不同的任务我们可以使用优先级队列来处理,让优先级较高的任务先执行,但需要注意的是,如果有优先级高的任务一直在队列中,那么优先级低的任务永远可能不执行
- 任务的依赖性,比如依赖数据库连接池的任务,线程提交sql后需要等待数据库返回结果,等待的时间越长,CPU利用率越低,此时和IO密集型相似,将线程数设置的更大,这样才能更好的利用CPU
三 使用Executors工具类创建线程池
- 创建无大小限制的线程池:public static ExecutorService newCachedThreadPool()
-
创建固定大小的线程池:public static ExecutorService newFixedThreadPool(int
nThreads) - 单线程池:public static ExecutorService newSingleThreadExecutor()
源码分析
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
本质都是new ThreadPollExecutor
Executors默认快捷的创建方式一般来说是不建议使用的,本质创建线程池的时候,没有指定创建线程的工厂类以及指定的拒绝策略,如果提交任务的速度比执行任务的速度快,阻塞队列中的任务堆积会越来越多,如果超过java进程的内存,可能会导致OOM,使得整个java进程垮掉
版权声明:本文为supermanman_原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。