在执行一个异步任务或并发任务时,每每是经过直接new Thread()方法来建立新的线程,这样作弊端较多,更好的解决方案是合理地利用线程池,线程池的优点很明显,以下:数组
下降系统资源消耗,经过重用已存在的线程,下降线程建立和销毁形成的消耗; 提升系统响应速度,当有任务到达时,无需等待新线程的建立便能当即执行; 方便线程并发数的管控,线程如果无限制的建立,不只会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等情况,从而下降系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率; 更强大的功能,线程池提供了定时、按期以及可控线程数等功能的线程池,使用方便简单。缓存
Java API针对不一样需求,利用Executors类提供了4种不一样的线程池:newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor,接下来说讲线程池的用法。服务器
2.1 newCachedThreadPool多线程
建立一个可缓存的无界线程池,该方法无参数。当线程池中的线程空闲时间超过60s则会自动回收该线程,当任务超过线程池的线程数则建立新线程。线程池的大小上限为Integer.MAX_VALUE,可看作是无限大。并发
public void cachedThreadPoolDemo(){ ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) { final int index = i; cachedThreadPool.execute(new Runnable() { [@Override] public void run() { System.out.println(Thread.currentThread().getName()+", index="+index); } }); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果:异步
pool-1-thread-1, index=0 pool-1-thread-1, index=1 pool-1-thread-1, index=2 pool-1-thread-1, index=3 pool-1-thread-1, index=4
从运行结果能够看出,整个过程都在同一个线程pool-1-thread-1中运行,后面线程复用前面的线程。ide
2.2 newFixedThreadPool优化
建立一个固定大小的线程池,该方法可指定线程池的固定大小,对于超出的线程会在LinkedBlockingQueue队列中等待。this
public void fixedThreadPoolDemo(){ ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3); for (int i = 0; i < 6; i++) { final int index = i; fixedThreadPool.execute(new Runnable() { [@Override] public void run() { System.out.println(Thread.currentThread().getName()+", index="+index); } }); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果:操作系统
pool-1-thread-1, index=0 pool-1-thread-2, index=1 pool-1-thread-3, index=2 pool-1-thread-1, index=3 pool-1-thread-2, index=4 pool-1-thread-3, index=5
从运行结果能够看出,线程池大小为3,每休眠1s后将任务提交给线程池的各个线程轮番交错地执行。线程池的大小设置,可参考Runtime.getRuntime().availableProcessors()。
2.3 newSingleThreadExecutor
建立一个只有线程的线程池,该方法无参数,全部任务都保存队列LinkedBlockingQueue中,等待惟一的单线程来执行任务,并保证全部任务按照指定顺序(FIFO或优先级)执行。
public void singleThreadExecutorDemo(){ ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 3; i++) { final int index = i; singleThreadExecutor.execute(new Runnable() { [@Override] public void run() { System.out.println(Thread.currentThread().getName()+", index="+index); } }); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果:
pool-1-thread-1, index=0 pool-1-thread-1, index=1 pool-1-thread-1, index=2
从运行结果能够看出,全部任务都是在单一线程运行的。
2.4 newScheduledThreadPool
建立一个可定时执行或周期执行任务的线程池,该方法可指定线程池的核心线程个数。
public void scheduledThreadPoolDemo(){ ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3); //定时执行一次的任务,延迟1s后执行 scheduledThreadPool.schedule(new Runnable() { [@Override] public void run() { System.out.println(Thread.currentThread().getName()+", delay 1s"); } }, 1, TimeUnit.SECONDS); //周期性地执行任务,延迟2s后,每3s一次地周期性执行任务 scheduledThreadPool.scheduleAtFixedRate(new Runnable() { [@Override] public void run() { System.out.println(Thread.currentThread().getName()+", every 3s"); } }, 2, 3, TimeUnit.SECONDS); }
运行结果:
pool-1-thread-1, delay 1s pool-1-thread-1, every 3s pool-1-thread-2, every 3s pool-1-thread-2, every 3s ...
schedule(Runnable command, long delay, TimeUnit unit),延迟必定时间后执行Runnable任务; schedule(Callable callable, long delay, TimeUnit unit),延迟必定时间后执行Callable任务; scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit),延迟必定时间后,以间隔period时间的频率周期性地执行任务; scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit),与scheduleAtFixedRate()方法很相似,可是不一样的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,也就是这一些任务系列的触发时间都是可预知的。 ScheduledExecutorService功能强大,对于定时执行的任务,建议多采用该方法。
2.5 方法对比
上述4个方法的参数对比,以下:
输入图片说明
其余参数都相同,其中线程工厂的默认类为DefaultThreadFactory,线程饱和的默认策略为ThreadPoolExecutor.AbortPolicy。
Executors类提供4个静态工厂方法:newCachedThreadPool()、newFixedThreadPool(int)、newSingleThreadExecutor和newScheduledThreadPool(int)。这些方法最终都是经过ThreadPoolExecutor类来完成的,这里强烈建议你们直接使用Executors类提供的便捷的工厂方法,能完成绝大多数的用户场景,当须要更细节地调整配置,须要先了解每一项参数的意义。
3.1 ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
建立线程池,在构造一个新的线程池时,必须知足下面的条件:
corePoolSize(线程池基本大小)必须大于或等于0; maximumPoolSize(线程池最大大小)必须大于或等于1; maximumPoolSize必须大于或等于corePoolSize; keepAliveTime(线程存活保持时间)必须大于或等于0; workQueue(任务队列)不能为空; threadFactory(线程工厂)不能为空,默认为DefaultThreadFactory类 handler(线程饱和策略)不能为空,默认策略为ThreadPoolExecutor.AbortPolicy。
3.2 参数详解
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.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
参数说明:
corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已建立的线程数小于corePoolSize,即使此时存在空闲线程,也会经过建立一个新线程来执行该任务,直到已建立的线程数大于或等于corePoolSize时,才会根据是否存在空闲线程,来决定是否须要建立新的线程。除了利用提交新任务来建立和启动线程(按需构造),也能够经过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提早启动线程池中的基本线程。 maximumPoolSize(线程池最大大小):线程池所容许的最大线程个数。当队列满了,且已建立的线程数小于maximumPoolSize,则线程池会建立新的线程来执行任务。另外,对于无界队列,可忽略该参数。
keepAliveTime(线程存活保持时间):默认状况下,当线程池的线程个数多于corePoolSize时,线程的空闲时间超过keepAliveTime则会终止。但只要keepAliveTime大于0,allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。另外,也可使用setKeepAliveTime()动态地更改参数。 unit(存活时间的单位):时间单位,分为7类,从细到粗顺序:NANOSECONDS(纳秒),MICROSECONDS(微妙),MILLISECONDS(毫秒),SECONDS(秒),MINUTES(分),HOURS(小时),DAYS(天);
workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。可使用此队列与线程池进行交互: 若是运行的线程数少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。 若是运行的线程数等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。 若是没法将请求加入队列,则建立新的线程,除非建立此线程超出 maximumPoolSize,在这种状况下,任务将被拒绝。 threadFactory(线程工厂):用于建立新线程。由同一个threadFactory建立的线程,属于同一个ThreadGroup,建立的线程优先级都为Thread.NORM_PRIORITY,以及是非守护进程状态。threadFactory建立的线程也是采用new Thread()方式,threadFactory建立的线程名都具备统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号); handler(线程饱和策略):当线程池和队列都满了,则代表该线程池已达饱和状态。 ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝,则直接抛出运行时异常 RejectedExecutionException。(默认策略) ThreadPoolExecutor.CallerRunsPolicy:调用者所在线程来运行该任务,此策略提供简单的反馈控制机制,可以减缓新任务的提交速度。 ThreadPoolExecutor.DiscardPolicy:没法执行的任务将被删除。 ThreadPoolExecutor.DiscardOldestPolicy:若是执行程序还没有关闭,则位于工做队列头部的任务将被删除,而后从新尝试执行任务(若是再次失败,则重复此过程)。 排队有三种通用策略:
直接提交。工做队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,若是不存在可用于当即运行任务的线程,则试图把任务加入队列将失败,所以会构造一个新的线程。此策略能够避免在处理可能具备内部依赖性的请求集时出现锁。直接提交一般要求无界 maximumPoolSizes 以免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略容许无界线程具备增加的可能性。 无界队列。使用无界队列(例如,不具备预约义容量的 LinkedBlockingQueue)将致使在全部 corePoolSize 线程都忙时新任务在队列中等待。这样,建立的线程就不会超过 corePoolSize。(所以,maximumPoolSize 的值也就无效了。)当每一个任务彻底独立于其余任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略容许无界线程具备增加的可能性。 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,可是可能较难调整和控制。队列大小和最大池大小可能须要相互折衷:使用大型队列和小型池能够最大限度地下降 CPU 使用率、操做系统资源和上下文切换开销,可是可能致使人工下降吞吐量。若是任务频繁阻塞(例如,若是它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列一般要求较大的池大小,CPU 使用率较高,可是可能遇到不可接受的调度开销,这样也会下降吞吐量。
3.3 工做队列BlockingQueue
BlockingQueue的插入/移除/检查这些方法,对于不能当即知足但可能在未来某一时刻能够知足的操做,共有4种不一样的处理方式:第一种是抛出一个异常,第二种是返回一个特殊值(null 或 false,具体取决于操做),第三种是在操做能够成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。以下表格:
操做 抛出异常 特殊值 阻塞 超时 插入 add(e) offer(e) put(e) offer(e, time, unit) 移除 remove() poll() take() poll(time, unit) 检查 element() peek() 不可用 不可用
实现BlockingQueue接口的常见类以下:
ArrayBlockingQueue:基于数组的有界阻塞队列。队列按FIFO原则对元素进行排序,队列头部是在队列中存活时间最长的元素,队尾则是存在时间最短的元素。新元素插入到队列的尾部,队列获取操做则是从队列头部开始得到元素。 这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦建立了这样的缓存区,就不能再增长其容量。试图向已满队列中放入元素会致使操做受阻塞;试图从空队列中提取元素将致使相似阻塞。ArrayBlockingQueue构造方法可经过设置fairness参数来选择是否采用公平策略,公平性一般会下降吞吐量,但也减小了可变性和避免了“不平衡性”,可根据状况来决策。
LinkedBlockingQueue:基于链表的无界阻塞队列。与ArrayBlockingQueue同样采用FIFO原则对元素进行排序。基于链表的队列吞吐量一般要高于基于数组的队列。
SynchronousQueue:同步的阻塞队列。其中每一个插入操做必须等待另外一个线程的对应移除操做,等待过程一直处于阻塞状态,同理,每个移除操做必须等到另外一个线程的对应插入操做。SynchronousQueue没有任何容量。不能在同步队列上进行 peek,由于仅在试图要移除元素时,该元素才存在;除非另外一个线程试图移除某个元素,不然也不能(使用任何方法)插入元素;也不能迭代队列,由于其中没有元素可用于迭代。Executors.newCachedThreadPool使用了该队列。
PriorityBlockingQueue:基于优先级的无界阻塞队列。优先级队列的元素按照其天然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。优先级队列不容许使用 null 元素。依靠天然顺序的优先级队列还不容许插入不可比较的对象(这样作可能致使 ClassCastException)。虽然此队列逻辑上是无界的,可是资源被耗尽时试图执行 add 操做也将失败(致使 OutOfMemoryError)。
3.4 线程池关闭
调用线程池的shutdown()或shutdownNow()方法来关闭线程池
shutdown原理:将线程池状态设置成SHUTDOWN状态,而后中断全部没有正在执行任务的线程。 shutdownNow原理:将线程池的状态设置成STOP状态,而后中断全部任务(包括正在执行的)的线程,并返回等待执行任务的列表。 中断采用interrupt方法,因此没法响应中断的任务可能永远没法终止。但调用上述的两个关闭之一,isShutdown()方法返回值为true,当全部任务都已关闭,表示线程池关闭完成,则isTerminated()方法返回值为true。当须要马上中断全部的线程,不必定须要执行完任务,可直接调用shutdownNow()方法。
3.5 线程池流程
输入图片说明
判断核心线程池是否已满,即已建立线程数是否小于corePoolSize?没满则建立一个新的工做线程来执行任务。已满则进入下个流程。 判断工做队列是否已满?没满则将新提交的任务添加在工做队列,等待执行。已满则进入下个流程。 判断整个线程池是否已满,即已建立线程数是否小于maximumPoolSize?没满则建立一个新的工做线程来执行任务,已满则交给饱和策略来处理这个任务。
4.1 合理地配置线程池
须要针对具体状况而具体处理,不一样的任务类别应采用不一样规模的线程池,任务类别可划分为CPU密集型任务、IO密集型任务和混合型任务。
对于CPU密集型任务:线程池中线程个数应尽可能少,不该大于CPU核心数; 对于IO密集型任务:因为IO操做速度远低于CPU速度,那么在运行这类任务时,CPU绝大多数时间处于空闲状态,那么线程池能够配置尽可能多些的线程,以提升CPU利用率; 对于混合型任务:能够拆分为CPU密集型任务和IO密集型任务,当这两类任务执行时间相差无几时,经过拆分再执行的吞吐率高于串行执行的吞吐率,但若这两类任务执行时间有数据级的差距,那么没有拆分的意义。 4.2 线程池监控
利用线程池提供的参数进行监控,参数以下:
Java线程池源码剖析(ThreadPoolExecutor)
线程池里的大学问:分析Java线程池的建立
聊聊并发(三)Java线程池的分析和使用