电子商务网站建设陈建祥,新闻发布会新闻通稿,公司网站成本,网站建设投标方案前文提到线程的使用以及线程间通信方式#xff0c;通常情况下我们通过new Thread或者new Runnable创建线程#xff0c;这种情况下#xff0c;需要开发者手动管理线程的创建和回收#xff0c;线程对象没有复用#xff0c;大量的线程对象创建与销毁会引起频繁GC#xff0c;…前文提到线程的使用以及线程间通信方式通常情况下我们通过new Thread或者new Runnable创建线程这种情况下需要开发者手动管理线程的创建和回收线程对象没有复用大量的线程对象创建与销毁会引起频繁GC那么事否有机制自动进行线程的创建管理和回收呢线程池可以实现该能力。
线程池的优点
线程池中线程重用避免线程创建和销毁带来的性能开销能有效控制线程数量避免大量线程抢占资源造成阻塞对线程进行简单管理提供定时执行预计指定间隔执行等策略
线程池的封装实现
在java.util.concurrent包中提供了一系列的工具类以方便开发者创建和使用线程池这些类的继承关系及说明如下 类名说明备注ExecutorExecutor接口提供了一种任务提交后的执行机制包括线程的创建与运行线程调度等通常不直接使用该类/ExecutorServiceExecutorService接口提供了创建管理终止Future执行的方法用于跟踪一个或多个异步任务的进度通常不直接使用该类/ScheduledExecutorServiceExecutorService的实现接口提供延时周期性执行Future的能力同时具备ExecutorService的基础能力通常不直接使用该类/AbstractExecutorServiceAbstractExecutorService是个虚类对ExecutorService中方法进行了默认实现其提供了newTaskFor函数用于获取RunnableFuture对象该对象实现了submitinvokeAny和invokeAll方法通常不直接使用该类/ThreadPoolExecutor通过创建该类对象就可以构建一个线程池通过调用execute方法可以向该线程池提交任务。通常情况下开发者通过自定义参数构造该类对象就来获得一个符合业务需求的线程池/ScheduledThreadPoolExecutor通过创建该类对象就可以构建一个可以周期性执行任务的线程池通过调用schedulescheduleWithFixedDelay等方法可以向该线程池提交任务并在指定时间节点运行。通常情况下开发者通过构造该类对象就来获得一个符合业务需求的可周期性执行任务的线程池/
由上表可知对于开发者而言通常情况下我们可以通过构造ThreadPoolExecutor对象来获取一个线程池对象通过其定义的execute方法来向该线程池提交任务并执行那么怎么创建线程池呢让我们一起看下
ThreadPoolExecutor
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,BlockingQueueRunnable 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;}从上述代码可以看出在构建ThreadPoolExecutor时主要涉及以下参数
corePoolSize核心线程个数一般情况下可以使用 处理器个数/2 作为核心线程数的取值可以通过Runtime.getRuntime().availableProcessors()来获取处理器个数maximumPoolSize最大线程个数该线程池支持同时存在的最大线程数量keepAliveTime非核心线程闲置时的超时时长超过这个时长非核心线程就会被回收我们也可以通过allowCoreThreadTimeOut(true)来设置核心线程闲置时在超时时间到达后回收unitkeepAliveTime的时间单位workQueue线程池中的任务队列当核心线程数满或最大线程数满时通过线程池的execute方法提交的Runnable对象存储在这个参数中遵循先进先出原则threadFactory创建线程的工厂 用于批量创建线程统一在创建线程时进行一些初始化设置如是否守护线程、线程的优先级等。不指定时默认使用Executors.defaultThreadFactory() 来创建线程线程具有相同的NORM_PRIORITY优先级并且是非守护线程handler任务拒绝处理策略当线程数量等于最大线程数且等待队列已满时就会采用拒绝处理策略处理新提交的任务不指定时默认的处理策略是AbortPolicy即抛弃该任务
综上我们可以看出创建一个线程池最少需要明确核心线程数最大线程数超时时间及单位等待队列这五个参数下面我们创建一个核心线程数为1最大线程数为35s超时回收等待队列最多能存放5个任务的线程池代码如下
ThreadPoolExecutor executor new ThreadPoolExecutor(1,3,5,TimeUnit.SECONDS,new LinkedBlockingQueue(5));随后我们使用for循环向该executor中提交任务代码如下
public static void main(String[] args) {// 创建线程池ThreadPoolExecutor executor new ThreadPoolExecutor(1,3,5,TimeUnit.SECONDS,new LinkedBlockingQueue(5));for (int i0;i10;i) {int finalI i;System.out.println(put runnable finalI to executor);// 向线程池提交任务executor.execute(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName(),runnable finalI start);try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName(),runnable finalI executed);}});}
}输出如下 从输出可以看到当提交一个任务到线程池时其执行流程如下 线程池拒绝策略
线程池拒绝策略有四类定义在ThreadPoolExecutor中分别是
AbortPolicy默认拒绝策略丢弃提交的任务并抛出RejectedExecutionException在该异常输出信息中可以看到当前线程池状态DiscardPolicy丢弃新来的任务但是不抛出异常DiscardOldestPolicy丢弃队列头部的旧任务然后尝试重新执行如果再次失败重复该过程CallerRunsPolicy由调用线程处理该任务
当然如果上述拒绝策略不能满足需求我们也可以自定义异常实现RejectedExecutionHandler接口即可创建自己的线程池拒绝策略下面是使用自定义拒绝策略的示例代码
public static void main(String[] args) {RejectedExecutionHandler handler new RejectedExecutionHandler() {Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println(runnable r in executor executor is refused);}};ThreadPoolExecutor executor new ThreadPoolExecutor(1,3,5,TimeUnit.SECONDS,new LinkedBlockingQueue(5),handler);for (int i0;i10;i) {int finalI i;Runnable runnable new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName(),runnable finalI start);try {Thread.sleep(5000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName(),runnable finalI executed);}};System.out.println(put runnable runnable index:finalI to executor:executor);executor.execute(runnable);}
}输出如下 任务队列
对于线程池而言任务队列需要是BlockingQueue的实现类BlockingQueue接口的实现类类图如下 下面我们针对常用队列做简单了解 ArrayBlockingQueueArrayBlockingQueue是基于数组的阻塞队列在其内部维护一个定长数组所以使用ArrayBlockingQueue时必须指定任务队列长度因为不论对数据的写入或者读取都使用的是同一个锁对象所以没有实现读写分离同时在创建时我们可以指定锁内部是否采用公平锁默认实现是非公平锁。 非公平锁与公平锁 公平锁多个任务阻塞在同一锁时等待时长长的优先获取锁 非公平锁多个任务阻塞在同一锁时锁可获取时一起抢锁谁先抢到谁先执行 LinkedBlockingQueueLinkedBlockingQueue是基于链表的阻塞队列在创建时可不指定任务队列长度默认值是Integer.MAX_VALUE在LinkedBlockingQueue中读锁和写锁实现了分支相对ArrayBlockingQueue而言效率提升明显。 SynchronousQueueSynchronousQueue是一个不存储元素的阻塞队列也就是说当需要插入元素时必须等待上一个元素被移出否则不能插入其适用于任务多但是执行比较快的场景。 PriorityBlockingQueuePriorityBlockingQueue是一个支持指定优先即的阻塞队列默认初始化长度为11最大长度为Integer.MAX_VALUE - 8可以通过让装入队列的对象实现Comparable接口定义对象排序规则来指定队列中元素优先级优先级高的元素会被优先取出。 DelayQueueDelayQueue是一个带有延迟时间的阻塞队列队列中的元素只有等待延时时间到了才可以被取出由于其内部用PriorityBlockingQueue维护数据故其长度与PriorityBlockingQueue一致。一般用于定时调度类任务。
下表从一些角度对上述队列进行了比较
队列名称底层数据结构默认长度最大长度是否读写分离适用场景ArrayBlockingQueue数组0开发者指定大小否任务数量较少时使用LinkedBlockingQueue链表Integer.MAX_VALUEInteger.MAX_VALUE是大量任务时使用SynchronousQueue公平锁-队列/非公平锁-栈0/否任务多但是执行速度快的场景PriorityBlockingQueue对象数组11Integer.MAX_VALUE-8否有任务需要优先处理的场景DelayQueue对象数组11Integer.MAX_VALUE-8否定时调度类场景