20210728Java线程池
编辑时间:2021/07/28
读完本节:大概花费不到20分钟,共2469词
1.使用线程池的优点
-
降低资源销毁
通过重复利用已经创建的线程,降低线程创建和销毁造成的消耗
-
提高响应速度
当任务到达时,任务可以不需要等到线程创建就能立即执行
-
防止服务器过载
形成内存溢出,或者CPU耗尽。
-
提高线程的可管理性
线程是稀缺资源,如果无限制地创建,不仅会消耗资源,还会降低系统的稳定性,使用线程池可以进行统一的分配、调优和监控。
2.线程池的状态
-
SHUT DOWN
表示不接受新任务,但可执行队列中的任务
-
TIDYING
所有任务已经中止,且工作线程数量为0
-
RUNNING
表示可接受新任务,且可执行队列中的任务
-
STOP
不接受新任务,且不再执行队列中的任务,且中断正在执行的任务
-
TERMINATED
中止状态
-
线程池的状态有限机
3.线程池的分类
-
ThreadPoolExecutor
-
ThreadPoolExecutor的继承关系:
ThreadPoolExecutor继承于AbstractExecutorService,AbstractExecutorService继承于ExecutorService,ExecutorService继承于Executor
-
ThreadPoolExecutor的其中一个构造方法
/** * Creates a new {@code ThreadPoolExecutor} with the given initial * parameters. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @param maximumPoolSize the maximum number of threads to allow in the * pool * @param keepAliveTime when the number of threads is greater than * the core, this is the maximum time that excess idle threads * will wait for new tasks before terminating. * @param unit the time unit for the {@code keepAliveTime} argument * @param workQueue the queue to use for holding tasks before they are * executed. This queue will hold only the {@code Runnable} * tasks submitted by the {@code execute} method. * @param threadFactory the factory to use when the executor * creates a new thread * @param handler the handler to use when execution is blocked * because the thread bounds and queue capacities are reached * @throws IllegalArgumentException if one of the following holds:<br> * {@code corePoolSize < 0}<br> * {@code keepAliveTime < 0}<br> * {@code maximumPoolSize <= 0}<br> * {@code maximumPoolSize < corePoolSize} * @throws NullPointerException if {@code workQueue} * or {@code threadFactory} or {@code handler} is null */ 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:线程池中除了核心线程之外的其他的最长可以保留的时间,因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间
util:计算时间的一个单位
workQueue:就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)
handler:拒绝策略
threadFactory:一个用于创建新线程的工厂
-
线程池一个任务的提交过程
-
线程池的拒绝策略
如何触发拒绝策略:当任务队列满了之后,如果还有任务提交过来,就会触发拒绝策略
四种常用的拒绝策略:
pAbortPolicy:不执行新任务,直接抛出异常,提示线程池已满,默认该方式。
pCallerRunsPolicy:直接调用execute来执行当前任务。
pDiscardPolicy:丢弃任务,但是不抛出异常。
pDiscardOldestPolicy:抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。先从任务队列中弹出最先加入的任务,空出一个位置,然后再次执行execute方法把任务加入队列。
-
四种常用的线程池
-
newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
底层源码实际上是new了一个ThreadPoolExecutor,它没有核心线程,最大线程可以有多个,然后保活60秒,60秒内无使用,就被回收。它的任务队列用的是SynchronousQueue。没有指定它的线程工厂,它的线程工厂用的是默认的线程工厂。也没有拒绝策略,使用默认拒绝策略。
newCachedThreadPool的特点:一旦收到新任务就必须马上执行,没有线程空着就new线程,这样会导致启动的线程特别多,基本没有上限,导致系统开销大,但是能够迅速响应线程执行的要求
使用举例
ExecutorService pool = Executors.newCachedThreadPool(); for(int i = 0; i < 10; i++){ Thread.sleep(200); pool.execute(()->{ for(int s = 0; s < 10; s++){ System.out.println(Thread.currentThread.getName() + ":" s); try{ Thread.sleep(200); }catch(InterruptedException e){ e.printStackTrace(); } } }); }
-
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
FixedThreadPool到底有几个线程是由核心线程数和最大线程数确定的。底层源码
/** * Creates a thread pool that reuses a fixed number of threads * operating off a shared unbounded queue. At any point, at most * {@code nThreads} threads will be active processing tasks. * If additional tasks are submitted when all threads are active, * they will wait in the queue until a thread is available. * If any thread terminates due to a failure during execution * prior to shutdown, a new one will take its place if needed to * execute subsequent tasks. The threads in the pool will exist * until it is explicitly {@link ExecutorService#shutdown shutdown}. * * @param nThreads the number of threads in the pool * @return the newly created thread pool * @throws IllegalArgumentException if {@code nThreads <= 0} */ public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
底层实际还是使用ThreadPoolExecutor创建线程池,但是corePoolSize大小和maximumPoolSize都被确定了,也因为如此不需要进行回收,所以保活时间keepAliveTime设置为0L。
newFixedThreadPool使用举例
ExecutorService pool = Executors.newFixedThreadPool(2); for(int i = 0; i < 10; i++){ Thread.sleep(200); pool.execute(()->{ for(int s = 0; s < 10; s++){ System.out.println(Thread.currentThread.getName() + ":" s); try{ Thread.sleep(200); }catch(InterruptedException e){ e.printStackTrace(); } } }); }
-
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
newScheduledThreadPool使用举例
ScheduledExecutorService pool = Executors.newScheduledThreadPool(5); int t = 0; pool.execute(()->{ System.out.println( Thread.currentThread.getName() + ":" + t, 0, 2, TimeUnit.SECONDS); });
-
newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newSingleThreadExecutor使用举例
ExecutorService pool = Executors.newSingleThreadExecutor(); for(int i = 0; i < 10; i++){ Thread.sleep(200); pool.execute(()->{ for(int s = 0; s < 10; s++){ System.out.println(Thread.currentThread.getName() + ":" s); try{ Thread.sleep(200); }catch(InterruptedException e){ e.printStackTrace(); } } }); }
-
-
-
ForkJoinPool
思想:将一个任务划分成若干个子任务,然后分别在多线程的情况下运行,最后汇总结果并输出
使用ThreadPoolExecutor定义任务的时候是从Runnable来继承,在实现ForkJoinPool的时候需要定义称谓特定的task类型,这个类型必须得能进行分叉的任务,所以将这个task定义成是一种特殊类型的任务,叫做ForkJoinTask。实际上,这个ForkJoinTask比较原始,可以用RecursiveAction。RecursiveAction其中一种叫RecursiveAction递归,称为递归的原因是:大任务分解成小任务,一直可以切到满足条件为止,这其中隐含了一个递归过程,因此叫RecursiveAction,这个RecursiveAction是不带返回值的任务。
4.如何调整线程池的大小
- 如果线程池中的线程数量过多,最终他们会竞争稀缺的处理器和内存资源,浪费大量的时间在上下文切换上,反之,如果线程池的数目过少,导致一些核可能无法充分利用。线程池的大小与处理器的利用率之比可以使用下面的公式进行估算
Nthread = NCPU * UCPU * (1 + W/C)
其中
NCPU是处理器的核心数目,可以通过Runtime.getRuntime().availableProcessors()获取
UCPU是期望的CPU利用率,这个值介于0和1之间
W/C是等待时间与计算时间的比率
5.线程池的使用情景
-
高并发、任务执行时间短的业务怎样使用线程池?
高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
-
并发不高、任务执行时间长的业务怎样使用线程池?
假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务。
假如是业务时间长集中在计算操作上,也就是计算密集型任务,线程池中的线程数设置得少一些,减少线程上下文的切换
6.线程池的提交方式
-
submit
-
submit既可以提交Runnable类型的任务,也可以提交Callable类型的任务,会有一个类型为Future的返回值,但当任务类型为Runnable时,返回值为null。
-
submit不会直接抛出,只有在使用Future的get方法获取返回值时,才会抛出异常
-
使用
Executor pool = Executors.newFixedThreadPool(10); //runnable 拉姆达表达式,包含返回值 Future<?> submit = pool.submit(()->{ System.out.printn( Thread.currentThread().getName() + ">>> test submit..." ); });
-
-
execute
-
execute只能提交Runnable类型的任务,无返回值。
-
execute在执行任务时,如果遇到异常会直接抛出
-
使用
Executor pool = Executors.newFixedThreadPool(10); //runnable 拉姆达表达式 pool.execute(()->{ System.out.printn( Thread.currentThread().getName() + ">>> test execute..." ); });
-