单页面网站入侵,青岛seo培训,锡盟建设工程造价信息网站,旅游景区网站建设规划方案线程池 七、线程池7.1线程池的概述7.2线程池的构建与参数ThreadPoolExecutor 的构造方法核心参数线程池的工作原理 Executors构造方法newFixedThreadPoolnewCachedThreadPoolnewSingleThreadExecutornewScheduledThreadPool(int corePoolSize) 为什么不推荐使用内置线程池… 线程池 七、线程池7.1线程池的概述7.2线程池的构建与参数ThreadPoolExecutor 的构造方法核心参数线程池的工作原理 Executors构造方法newFixedThreadPoolnewCachedThreadPoolnewSingleThreadExecutornewScheduledThreadPool(int corePoolSize) 为什么不推荐使用内置线程池 7.3线程池中线程异常后销毁还是复用7.4如何给线程池命名 七、线程池
7.1线程池的概述
定义线程池是一个容纳多个线程的容器线程池中的线程可以被重复利用从而避免了频繁创建和销毁线程带来的性能开销。线程池的作用 降低资源消耗通过线程复用避免了频繁的线程创建和销毁提高了资源利用率。提高响应速度当任务到达时如果有空闲线程则可以立即执行避免了线程的创建和销毁延迟。提高线程的可管理性通过对线程的管理避免了无限制地创建线程增强了系统的稳定性和可控性。 核心思想线程池的核心思想是 线程复用即通过重复使用现有线程来处理多个任务。
7.2线程池的构建与参数
ThreadPoolExecutor 的构造方法
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueueRunnable workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)核心参数
corePoolSize核心线程数即线程池中可以一直保持活动的线程数。maximumPoolSize最大线程数线程池中允许的最大线程数量。keepAliveTime非核心线程的空闲时间当线程池中的线程数量超过 corePoolSize 且任务完成时超过的线程在空闲时会被销毁。workQueue任务队列用于存放等待执行的任务。threadFactory线程工厂用于创建新线程。handler拒绝策略定义了线程池满载时如何处理新的任务。
拒绝策略RejectedExecutionHandler:
AbortPolicy默认策略抛出 RejectedExecutionException 异常。CallerRunsPolicy将任务回退到调用者的线程来执行。DiscardPolicy直接丢弃任务不抛出任何异常。DiscardOldestPolicy丢弃队列中最早的任务尝试执行当前任务。
线程池的工作原理 1.线程池的初始化线程池创建后不会立刻创建线程只有在调用 execute() 方法时线程池才会开始创建线程。
2.任务提交过程 如果运行线程数小于 corePoolSize立即创建线程并执行任务。如果运行线程数大于或等于 corePoolSize则将任务放入任务队列。如果队列已满且运行线程数小于 maximumPoolSize则创建新的非核心线程执行任务。如果队列满且线程数已经达到 maximumPoolSize则执行拒绝策略。
3.线程空闲回收当线程空闲超过 keepAliveTime且线程池中的线程数大于 corePoolSize空闲线程会被销毁线程池会收缩到核心线程数的大小。
Executors构造方法
Executors 提供了四种线程池的创建newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool
newFixedThreadPool
newFixedThreadPool(int nThreads)创建一个固定大小的线程池适用于任务量已知且长期运行的场景。所有任务会被放入一个无界队列中任务执行较慢可能导致内存溢出。
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads, 0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable());
}特点 核心线程数 最大线程数没有救济线程被创建因此无需超时时间 LinkedBlockingQueue 是一个单向链表实现的阻塞队列默认大小为Integer.MAX_VALUE 也就是无界队列可以放任意数量的任务在任务比较多的时候会导致 OOM内存溢出 适用于任务量已知相对耗时的长期任务
newCachedThreadPool
newCachedThreadPool()创建一个可扩容的线程池适用于任务量不稳定且每个任务执行时间较短的场景。核心线程数为 0最大线程数为 Integer.MAX_VALUE可能会导致大量线程创建和 OOM。
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L,TimeUnit.SECONDS,new SynchronousQueueRunnable());
}特点
核心线程数是 0 最大线程数是 29 个 1全部都是救急线程60s 后可以回收可能会创建大量线程从而导致 OOMSynchronousQueue 作为阻塞队列没有容量没有线程来取是放不进去的类似一手交钱、一手交货适合任务数比较密集但每个任务执行时间较短的情况
newSingleThreadExecutor
newSingleThreadExecutor()创建一个只有一个线程的线程池适用于需要顺序执行任务的场景保证任务按顺序执行。
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueueRunnable()));
}使用场景
保证所有任务按照指定顺序执行线程数固定为 1任务数多于 1 时会放入无界队列排队任务执行完毕这唯一的线程也不会被释放
newScheduledThreadPool(int corePoolSize)
创建一个可以执行延时任务的线程池适用于定时任务和延时任务。
区别 创建一个单线程串行执行任务如果任务执行失败而终止那么没有任何补救措施线程池会新建一个线程保证池的正常工作 Executors.newSingleThreadExecutor() 线程个数始终为 1不能修改。 FinalizableDelegatedExecutorService 应用的是装饰器模式只对外暴露了 ExecutorService 接口因此不能调用 ThreadPoolExecutor 中特有的方法 原因父类不能直接调用子类中的方法需要反射或者创建对象的方式可以调用子类静态方法 Executors.newFixedThreadPool(1) 初始时为 1可以修改。对外暴露的是 ThreadPoolExecutor对象可以强转后调用 setCorePoolSize 等方法进行修改
为什么不推荐使用内置线程池
《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建而是通过 ThreadPoolExecutor 构造函数的方式这样的处理方式让写的同学更加明确线程池的运行规则规避资源耗尽的风险
Executors 返回线程池对象的弊端如下
FixedThreadPool 和 SingleThreadExecutor:使用的是有界阻塞队列是 LinkedBlockingQueue 其任务队列的最大长度为 Integer.MAX_VALUE 可能堆积大量的请求从而导致 OOM。CachedThreadPool:使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE 如果任务数量过多且执行速度较慢可能会创建大量的线程从而导致 OOM。ScheduledThreadPool 和 SingleThreadScheduledExecutor :使用的无界的延迟阻塞队列 DelayedWorkQueue 任务队列最大长度为 Integer.MAX_VALUE 可能堆积大量的请求从而导致 OOM。
7.3线程池中线程异常后销毁还是复用
execute() 提交任务时的异常处理
如果任务抛出异常当前线程会终止线程池会创建新线程来替换。
submit() 提交任务时的异常处理
如果任务抛出异常异常会被封装在 Future 对象中继续复用可以通过 Future.get() 获取异常信息。
7.4如何给线程池命名
初始化线程池的时候需要显示命名设置线程池名称前缀有利于定位问题。
默认情况下创建的线程名字类似 pool-1-thread-n 这样的没有业务含义不利于我们定位问题。
给线程池里的线程命名通常有下面两种方式
1、利用 guava 的 ThreadFactoryBuilder
ThreadFactory threadFactory new ThreadFactoryBuilder().setNameFormat(threadNamePrefix -%d).setDaemon(true).build();
ExecutorService threadPool new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory);2、自己实现 ThreadFactory。
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;/*** 线程工厂它设置线程名称有利于我们定位问题。*/
public final class NamingThreadFactory implements ThreadFactory {private final AtomicInteger threadNum new AtomicInteger();private final String name;/*** 创建一个带名字的线程池生产工厂*/public NamingThreadFactory(String name) {this.name name;}Overridepublic Thread newThread(Runnable r) {Thread t new Thread(r);t.setName(name [# threadNum.incrementAndGet() ]);return t;}
}