相比于线程池,我们可能接触new Thread更多一点,既然有了new Thread我们为什么还要使用线程池呢?
new Thread的弊端
a、每次new Thread新建对象,性能差
b、线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或者OOM(OutOfMemory)
c、缺少更多功能,如更多执行、定期执行、线程中断
线程池的优势
a、重用存在的线程,减少对象创建、消亡的开销,性能好
b、可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞
c、提供定时执行、定期执行、单线程、并发数控制等功能
线程池类图
由类图可以看出,ThreadPoolExecutor类是线程池中最核心的一个类。我们来做重点分析
ThreadPoolExecutor构造参数
a、corePoolSize:核心线程数量,默认情况下(可预创建线程)线程池后线程池中的线程数为0,当有任务后当有任务后就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列中
b、maximumPoolSize:线程最大线程数
c、workQueue:阻塞队列,存储等待执行的任务,有三种取值,ArrayBlockQueue(基于数组的先进先出队列,创建时必须指定大小)、LinkedBlockingQueue(基于链表的先进先出队列,如果没有指定此队列大小,默认为Integer.MAX_VALUE)、SynchronousQueue(不会保存提交的任务,直接新建一个线程来执行新的任务)
d、keepAliveTime:线程没有任务执行时最多保持多久时间终止,默认情况只有当线程池中的线程数大于corePoolSize时,keepAliveTIme才会起作用,当线程池中的线程数大于corePoolSize,如果一个线程的空闲时间达到keepAliveTime,则会终止。如果调用allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时keepAliveTime参数也会起作用,直到线程池的线程数为0
e、unit:keepAliveTime的时间单位,有7中取值,如:TimeUnit.DAYS; 天,可具体到纳秒
f、threadFactory:线程工厂,用来创建线程
g、rejectHandler:当拒绝处理任务时的策略,通常有四种取值,ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常;ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,重新尝试执行任务(重复此过程);ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
参数之间的关系如下:
a、如果当前poolsize小于corePoolSize,创建新线程执行任务
b、如果当前poolsize大于corePoolsize,且等待队列未满,进入等待队列
c、如果当前poolsize大于corePoolsize且小于maximumPoolSize,且等待队列已满,创建新线程执行任务
d、如果当前poolsize大于corePoolSize且大于maximumPoolSize,且等待队列已满则用拒绝策略来处理该任务
e、线程池中的线程执行完任务后不会立刻退出,而是去检查等待队列是否有新的线程去执行,如果在keepAliveTime里等不到新任务,线程就会退出
ThreadPoolExecutor状态
图中需要注意的是shutdown()和shutdownNow()方法的区别,执行前者后,还在执行的线程会执行完再关闭,执行后者后,线程池会立刻关闭,正在执行的线程不再执行
ThreadPoolExecutor方法
a、execute():提交任务,交给线程池执行
b、submit():提交任务,能够返回执行结果 execute+Future
c、shutdown():关闭线程池,等待任务都执行完
d、shutdownNow():关闭线程池,不等待任务执行完
e、getTaskCount():线程池已执行的和未执行的任务总数
f、getCompletedTaskCount():已完成的任务数量
g、getPoolSize():线程池当前线程数量
h、getActiveCount():当前线程池正在执行任务的线程数量
Executors类
由上面的类图可以看出,Executors类为Executor,ExecutorService,ScheduledExecutorService提供了一些工具方法,并且Executors封装了ScheduledThreadPoolExecutor类和ThreadPoolExecutor类,所以我们倡导在实际使用中使用此类,Executors类提供了四个静态方法:
a、newCachedThreadPool:创建可缓存线程池,灵活回收空闲线程,如果没有可回收线程,则创建新线程
由上图可看出,newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用SynchronousQueue,就是说有新任务就创新线程运行,线程空闲超过60秒,则销毁线程。demo代码如下:
public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { final int index = i; executorService.execute(new Runnable() { @Override public void run() { log.info("task:{}", index); } }); } executorService.shutdown(); }
b、newFixedThreadPool:创建定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值相等,线程空闲后直接销毁,使用的是LinkedBlockingQueue。demo代码如下:
public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3); for (int i = 0; i < 10; i++) { final int index = i; executorService.execute(new Runnable() { @Override public void run() { log.info("task:{}", index); } }); } executorService.shutdown(); }
c、newScheduledThreadPool:创建大小无限制线程池(最大为Integer.MAX_VALUE),支持定时及周期性任务执行
newScheduledThreadPool的特点是可以进行任务调度,最常用的方法是ScheduleAtFixedRate(基于固定时间间隔进行任务调度)和ScheduleWithFixedDelay(基于不固定时间间隔进行任务调度,主要取决于任务执行时间长短), demo代码如下:
public static void main(String[] args) { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); executorService.schedule(new Runnable() { @Override public void run() { log.warn("schedule run"); } },3,TimeUnit.SECONDS); executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { log.warn("schedule run"); } },1,3,TimeUnit.SECONDS); }
d、newSingleThreadExecutor:创建一个单线程化的线程池,只会用唯一的线程来执行任务
newSingleThreadExecutor将corePoolSize和maximumPoolSize都固定为1,线程空闲时直接销毁,使用的LinkedBlockingQueue.demo代码如下:
public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 10; i++) { final int index = i; executorService.execute(new Runnable() { @Override public void run() { log.info("task:{}", index); } }); } executorService.shutdown(); }
线程池合理配置
线程池的具体配合需要按照实际情况进行调整,以下为两条参考原则,可先按此原则设置再根据系统负载,资源利用情况进行调整。
a、cpu密集型任务,需要尽量压榨cpu,参考值可以设置为ncpu+1;
b、io密集型任务,参考值可以设置为2*ncpu
原文地址:https://www.cnblogs.com/sbrn/p/8974566.html