java线程池知识
java常用线程池知识
一,线程池创建方式
1.Executors
快捷创建线程池的方法:
ExecutorService FixedThreadPool = Executors.newFixedThreadPool(10);
ExecutorService CachedThreadPool = Executors.newCachedThreadPool();
ExecutorService ScheduledThreadPool = Executors.newScheduledThreadPool(10);
ExecutorService SingleThreadExceutor = Executors.newSingleThreadPool();
优点:快捷,简单
缺点:拒绝策略,阻塞队列闲置线程存活时间等参数,都是用Executors给的默认设置。无法满足特殊需求。
阿里巴巴开发手册,明确提出不允许使用此方法创建线程池,使用ThreadPoolExecutor创建线程 池,并手动设置线程池内线程数量,阻塞队列类型,大小。以及拒绝策略,方便后续维护和理解。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
这里的任务队列就是使用的 LinkedBlockingQueue 且是*队列,最大值是 integer 的最大值。如果有很多的任务,会OOM异常。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看出,各种线程的快捷创建方法,都是由ThreadPoolExecutor实现的。不如直接使用这个创建,别人一看就知道怎么回事, 也方便修改和配置。
2.ThreadPoolExecutor
LinkedBlockingDeque<Runnable> queue = new LinkedBlockingDeque<>(20);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler AbortPolicy =new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 2L,
TimeUnit.SECONDS, queue, threadFactory, AbortPolicy);
这段代码,是自定义线程池,7个参数分别是
- 1.线程最少线程数,初始线程数 (2)
- 2.线程池内最大线程数 (4)
- 3.闲置线程存活时间 (2L)
- 4.存活时间单位 (TimeUnit.SECONDS)
- 5.存放人物的队列 (queue)
- 6.创建线程的工厂 (threadFactory)
- 7.拒绝策略 (AbortPolicy)
二,线程池类型
1,FixedThreadPool
- 容量固定的线程池。使用LinkedBlockingQueue作为任务队列,当任务数量大于线程池容量的时候,未执行的任务进入任务等待队列LinkedBlockingQueue中,当线程有空闲的时候,自动从队列中取出任务执行。
- 使用场景: 大多数情况下,推荐使用的线程池。因为os系统和硬件是有线程上限限制的,不可能去无限的提供线程池操作。
2,CachedThreadPool
-
缓存线程池。容量 0-Integer.MAX_VALUE,自动根据任务数扩容:如果线程池中的线程数不满足任务执行需求,则创建新的线程并添加到池中。生命周期默认60s,当线程空闲时长到60s的时候,自动终止销毁释放线程,移除线程池。
-
使用场景 : 可用于测试最高负载量,用于对FixedThreadPool容量的参考。
注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT(默认60s)不活动,其会自动被终止
3,ScheduledThreadPool
- 定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
4,SingleThreadExceutor
- 单一容量线程池。
5,ForkJoinPool
- 拆分合并,将一个大的任务,拆分成若干子任务,并最终汇总子任务的执行结果,得到大任务的执行结果。并行执行,采用工作窃取机制,更加有效的利用cpu资源。
5.1、主要类
-
ForkJoinPool : 用于执行Task。任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他未完成工作线程的队列的尾部获取一个任务。
-
ForkJoinTask:ForkJoin任务,提供在任务中执行fork()和join()操作的机制(二叉分逻辑),通常不直接继承ForkJoinTask类,而是继承抽象子类RecursiveTask(有返回结果) 或者 RecursiveAction (无返回结果)。
-
ForkJoinWorkerThread:ForkJoinPool 内部的worker thread,用来具体执行ForkJoinTask。内部有
-
ForkJoinPool.WorkQueue,来保存要执行的 ForkJoinTask。
-
ForkJoinPool.WorkQueue:保存要执行的ForkJoinTask。
5.2、工作窃取机制
1、大任务分割成N个子任务,为避免线程竞争,于是分开几个队列去保存这些子任务,并为每个队列提供一个工作线程去处理其中的任务。工作线程与任务队列一一对应。
2、如果A线程执行完自己队列中的所有任务,如果此时其他队列中还有未执行的任务,则A线程会去窃取一个其他队列的任务来执行。但是,此时两个线程同时访问可能会产生竞争问题,所以,任务队列设计成了双向队列。A线程窃取的时候,从另一端开始执行,尽可能的去避免线程竞争问题。
3、工作窃取机制,充分的利用线程资源,并尽可能的去避免线程间的竞争问题。但是,只能是尽可能避免,并不能规避。例如,双向队列只有一个任务。
三,阻塞队列
1,LinkedBlockingQueue
2,ArrayBlockingQueue
3,SynchronousQueue
4,PriorityBlockingQueue
四,拒绝策略
拒绝策略提供*接口 RejectedExecutionHandler ,其中方法 rejectedExecution 即定制具体的拒绝策略的执行逻辑。
jdk默认提供了四种拒绝策略:
1,ThreadPoolExecutor.AbortPolicy()
- 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
2,ThreadPoolExecutor.CallerRunsPolicy
- 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
3,ThreadPoolExecutor.DiscardPolicy
- 直接丢弃,其他啥都没有
4,ThreadPoolExecutor.DiscardOldestPolicy
- 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入
五,线程工厂
1,Executors.defaultThreadFactory
- ThreadFactory是一个线程工厂。用来创建线程。这里为什么要使用线程工厂呢?其实就是为了统一在创建线程时设置一些参数,如是否守护线程。线程一些特性等,如优先级。通过这个TreadFactory创建出来的线程能保证有相同的特性。它首先是一个接口类,而且方法只有一个。就是创建一个线程。
public interface ThreadFactory {
Thread newThread(Runnable r);
}
2,Executors.privilegedThreadFactory
- 如果在应用程序中需要利用安全策略来控制对某些代码库的访问,那么可以通过Executors.privilegedThreadFactory来制定自己的线程工厂,通过这种方式创建的线程,将与创建privilegedThreadFactory的线程拥有相同的访问权限,AccessControlContext,contextClassLoader.
如果不使用privilegedThreadFactory,线程池创建的线程将从在需要新线程时调用excute 或submit 的客户端程序中继承访问权限。从而导致令人困惑的安全性异常。