共计 6007 个字符,预计需要花费 16 分钟才能阅读完成。
Java- 五种线程池,四种回绝策略,三种阻塞队列
- 三种阻塞队列:
BlockingQueue<Runnable> workQueue = null;
workQueue = new ArrayBlockingQueue<>(5);// 基于数组的先进先出队列,有界
workQueue = new LinkedBlockingQueue<>();// 基于链表的先进先出队列,无界
workQueue = new SynchronousQueue<>();// 无缓冲的期待队列,无界
- 四种回绝策略:
RejectedExecutionHandler rejected = null;
rejected = new ThreadPoolExecutor.AbortPolicy();// 默认,队列满了丢工作抛出异样
rejected = new ThreadPoolExecutor.DiscardPolicy();// 队列满了丢工作不异样
rejected = new ThreadPoolExecutor.DiscardOldestPolicy();// 将最早进入队列的工作删,之后再尝试退出队列
rejected = new ThreadPoolExecutor.CallerRunsPolicy();// 如果增加到线程池失败,那么主线程会本人去执行该工作
- 五种线程池:
ExecutorService threadPool = null;
threadPool = Executors.newCachedThreadPool();// 有缓冲的线程池,线程数 JVM 管制
threadPool = Executors.newFixedThreadPool(3);// 固定大小的线程池
threadPool = Executors.newScheduledThreadPool(2);
threadPool = Executors.newSingleThreadExecutor();// 单线程的线程池,只有一个线程在工作
threadPool = new ThreadPoolExecutor();// 默认线程池,可控制参数比拟多
1. 什么是线程池?
很简略,简略看名字就晓得是装有线程的池子,咱们能够把要执行的多线程交给线程池来解决,和连接池的概念一样,通过保护肯定数量的线程池来达到多个线程的复用。
2. 线程池的益处
咱们晓得不必线程池的话,每个线程都要通过 new Thread(xxRunnable).start()的形式来创立并运行一个线程,线程少的话这不会是问题,而实在环境可能会开启多个线程让零碎和程序达到最佳效率,当线程数达到肯定数量就会耗尽零碎的 CPU 和内存资源,也会造成 GC 频繁收集和进展,因为每次创立和销毁一个线程都是要耗费系统资源的,如果为每个工作都创立线程这无疑是一个很大的性能瓶颈
。所以,线程池中的线程复用极大节俭了系统资源,当线程一段时间不再有工作解决时它也会主动销毁,而不会长驻内存。
3. 线程池外围类 – 参数流程详解
在 java.util.concurrent 包中咱们能找到线程池的定义,其中 ThreadPoolExecutor
是咱们线程池外围类,首先看看线程池类的主要参数有哪些。
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory.
*/
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:空余线程存活工夫,指的是超过 corePoolSize 的空余线程达到多长时间才进行销毁。
- unit:销毁工夫单位。
- workQueue:存储期待执行线程的工作队列。
- threadFactory:创立线程的工厂,个别用默认即可。
- handler:回绝策略,当工作队列、线程池全已满时如何回绝新工作,默认抛出异样。
4. 线程池工作流程
- 1、如果线程池中的线程小于 corePoolSize 时就会创立新线程间接执行工作。
- 2、如果线程池中的线程大于 corePoolSize 时就会临时把工作存储到工作队列 workQueue 中期待执行。
- 3、如果工作队列 workQueue 也满时,当线程数小于最大线程池数 maximumPoolSize 时就会创立新线程来解决,而线程数大于等于最大线程池数 maximumPoolSize 时就会执行回绝策略。
5. 线程池分类
Executors
是 jdk 外面提供的创立线程池的工厂类,它默认提供了 4 种罕用的线程池利用,而不用咱们去反复结构。
newFixedThreadPool
固定线程池,外围线程数和最大线程数固定相等,而闲暇存活工夫为 0 毫秒,阐明此参数也无意义,工作队列为最大为 Integer.MAX_VALUE 大小的阻塞队列。当执行工作时,如果线程都很忙,就会丢到工作队列等有闲暇线程时再执行,队列满就执行默认的回绝策略。
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue, using the provided
* ThreadFactory to create new threads when needed.
*/
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
newCachedThreadPool
带缓冲线程池,从结构看外围线程数为 0,最大线程数为 Integer 最大值大小,超过 0 个的闲暇线程在 60 秒后销毁,SynchronousQueue 这是一个间接提交的队列,意味着每个新工作都会有线程来执行,如果线程池有可用线程则执行工作,没有的话就创立一个来执行,线程池中的线程数不确定,个别倡议执行速度较快较小的线程,不然这个最大线程池边界过大容易造成内存溢出。
/**
* Creates a thread pool that creates new threads as needed, but
* will reuse previously constructed threads when they are
* available. These pools will typically improve the performance
* of programs that execute many short-lived asynchronous tasks.
*/
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newSingleThreadExecutor
单线程线程池,外围线程数和最大线程数均为 1,闲暇线程存活 0 毫秒同样无意思,意味着每次只执行一个线程,多余的先存储到工作队列,一个一个执行,保障了线程的程序执行。
/**
* Creates an Executor that uses a single worker thread operating
* off an unbounded queue. (Note however that if this single
* thread terminates due to a failure during execution prior to
* shutdown, a new one will take its place if needed to execute
* subsequent tasks.) Tasks are guaranteed to execute
* sequentially, and no more than one task will be active at any
* given time. Unlike the otherwise equivalent
*/
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newScheduledThreadPool
调度线程池,即按肯定的周期执行工作,即定时工作,对 ThreadPoolExecutor 进行了包装而已。
/**
* Creates a thread pool that can schedule commands to run after a
* given delay, or to execute periodically.
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);
}
6. 回绝策略
- AbortPolicy
简略粗犷,间接抛出回绝异样,这也是默认的回绝策略。 - CallerRunsPolicy
如果线程池未敞开,则会在调用者线程中间接执行新工作,这会导致主线程提交线程性能变慢。 - DiscardPolicy
从办法看没做工作操作,即示意不解决新工作,即抛弃。 - DiscardOldestPolicy
摈弃最老的工作,就是从队列取出最老的工作而后放入新的工作进行执行。
7. 缓冲队列 BlockingQueue
-
缓冲队列 BlockingQueue 简介:
BlockingQueue 是双缓冲队列。BlockingQueue 外部应用两条队列,容许两个线程同时向队列一个存储,一个取出操作。在保障并发平安的同时,进步了队列的存取效率。
- 罕用的几种 BlockingQueue:
- ArrayBlockingQueue(int i): 规定大小的 BlockingQueue,其结构必须指定大小。其所含的对象是 FIFO 程序排序的。
- LinkedBlockingQueue()或者(int i): 大小不固定的 BlockingQueue,若其结构时指定大小,生成的 BlockingQueue 有大小限度,不指定大小,其大小有 Integer.MAX_VALUE 来决定。其所含的对象是 FIFO 程序排序的。
- PriorityBlockingQueue()或者(int i): 相似于 LinkedBlockingQueue,然而其所含对象的排序不是 FIFO,而是根据对象的天然程序或者构造函数的 Comparator 决定。
- SynchronizedQueue(): 非凡的 BlockingQueue,对其的操作必须是放和取交替实现。
8. 如何提交线程
能够先轻易定义一个固定大小的线程池
ExecutorService es = Executors.newFixedThreadPool(3);
提交一个线程
es.submit(xxRunnble);
es.execute(xxRunnble);
submit 和 execute 别离有什么区别呢?
-
- execute 没有返回值,如果不须要晓得线程的后果就应用 execute 办法,性能会好很多。
-
- submit 返回一个 Future 对象,如果想晓得线程后果就应用 submit 提交,而且它能在主线程中通过 Future 的 get 办法捕捉线程中的异样。
咱们来看看 execute()到底办法是如何解决的:
-
- 获取以后线程池的状态。
-
- 以后线程数量小于 coreSize 时创立一个新的线程运行。
-
- 如果以后线程处于运行状态,并且写入阻塞队列胜利。
-
- 双重查看,再次获取线程状态;如果线程状态变了(非运行状态)就须要从阻塞队列移除工作,并尝试判断线程是否全副执行结束,同时执行回绝策略。
-
- 如果以后线程池为空就新创建一个线程并执行。
-
- 如果在第三步的判断为非运行状态,尝试新建线程,如果失败则执行回绝策略。
9. 如何敞开线程池
es.shutdown();
不再承受新的工作,之前提交的工作等执行完结再敞开线程池。
es.shutdownNow();
不再承受新的工作,试图进行池中的工作再敞开线程池,返回所有未解决的线程 list 列表。
10. 总结
线程池次要用来解决线程生命周期开销问题和资源有余问题。通过对多个工作重复使用线程,线程创立的开销就被摊派到多个工作上,而且因为在申请达到时线程曾经存在,所以打消线程创立所带来的提早。这样,就能够立刻为申请服务,使应用程序响应更快。另外,通过适当的调整线程中的线程数目能够防止出现资源有余。