多线程技术点-JDK并发包-线程池-Executor框架
线程池概念:
为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用,所以需要线程池。
线程池中,总有几个活跃线程,当你需要使用线程时,可以从池子中随便拿一个空闲线程,完成工作时,
并没有关闭线程,而是将这个线程退回到池子中,方便其他人复用。
线程池实现:
JDK对线程池提供了一套Executor框架,帮助开发人员有效地进行线程池控制。
Executor框架详解
实现: Executors类是线程池工厂的角色,通过Executors可以获取一个拥有特定功能的线程池。
ThreadPoolExecutor实现了ExecutorService(继承Executor接口)接口,因此这个类实现了线程池调度的所有细节,
Executors工厂类就是代理ThreadPoolExecutor获取实例。
图解:
Executors工厂提供创建线程池实例的方法
1、newFixedThreadPool()
说明:该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。
若没有,则新的任务暂时存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
代码
1 package com.dsd.jdk.executor.executerservice; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 /** 7 * 固定线程数量的线程池例子 8 * @author daishengda 9 * 10 */ 11 public class ThreadPoolFixedDemo { 12 public static class MyTask implements Runnable { 13 14 @Override 15 public void run() { 16 System.out.println(System.currentTimeMillis() + ":Thread ID:" 17 + Thread.currentThread().getId()); 18 } 19 20 } 21 22 public static void main(String[] args) { 23 MyTask task = new MyTask(); 24 ExecutorService es = Executors.newFixedThreadPool(5); 25 for (int i = 0; i < 5; i++) { 26 es.submit(task); 27 } 28 } 29 }
2、newSingleThreadExecutor()
说明:该方法返回只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存到一个任务队列中,待线程空闲,
按先入先出的顺序执行队列中的任务。
3、newCachedThreadExector()
说明:该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会有些使用可复用的线程。
若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
4、newSingleThreadScheduledExecutor()
说明:该方法返回一个ScheduleExecutorService对象,线程池大小为1。ScheduleExecutorService接口在ExecutorService接口之上扩展了在
给定时间执行某任务的功能,如在某个固定的延时之后执行,或者周期性执行某个任务,实现定时任务功能。
5、newScheduleExecutor()
说明:该方法也返回一个ScheduleExecutorService对象,该线程池可以指定线程数量。
ScheduleExecutorService对象方法:
/** * 会在给定时间,对任务进行一次调度 * @param command 待调度线程 * @param delay 延迟时间 * @param unit 单位 * @return */ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit); /** * 对任务进行周期性的调度 * @param command 待调度线程 * @param initialDelay 初始延迟时间(第一个线程的延迟时间) * @param period 时间间隔 * @param unit 单位 * @return */ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); /** * 对任务进行周期性的调度 * @param command 待调度线程 * @param initialDelay 初始延迟时间(第一个线程的延迟时间) * @param delay 延迟时间间隔 * @param unit 单位 * @return */ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
注意:
scheduleAtFixedRate与scheduleWithFixedDelay方法是有区别的。
对于scheduleAtFixedRate,任务调度的频率是一定的,是以上一个任务开始执行的时间为起点,之后的period时间,调度下一次任务。
对于scheduleWithFixedDelay,实在上一次任务结束后,再经过delay时间进行任务调度。
所以两者的计算起点不一致。
例子
1 package com.dsd.jdk.executor.executerservice; 2 3 import java.util.concurrent.Executors; 4 import java.util.concurrent.ScheduledExecutorService; 5 import java.util.concurrent.TimeUnit; 6 7 /** 8 * 任务调度线程池例子 9 * @author daishengda 10 * 11 */ 12 public class ScheduleExecutorServiceDemo { 13 14 public static void main(String[] args) { 15 ScheduledExecutorService ses = Executors.newScheduledThreadPool(10); 16 ses.scheduleAtFixedRate(new Runnable() { 17 18 @Override 19 public void run() { 20 try { 21 TimeUnit.SECONDS.sleep(1); 22 System.out.println("FixedRate:"+System.currentTimeMillis()/1000); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 } 27 }, 0, 2, TimeUnit.SECONDS); 28 29 ses.scheduleWithFixedDelay(new Runnable() { 30 31 @Override 32 public void run() { 33 try { 34 TimeUnit.SECONDS.sleep(1); 35 System.out.println("FixedDelay:"+System.currentTimeMillis()/1000); 36 } catch (InterruptedException e) { 37 e.printStackTrace(); 38 } 39 } 40 }, 0, 2, TimeUnit.SECONDS); 41 } 42 }
核心线程池的内部实现-ThreadPoolExecutor
以上前三种类型的线程池,实际是对ThreadPoolExecutor类的封装。
/** * 返回一个corePoolSize与maximumPoolSize大小一样,并且使用LinkedBlockingQueue任务队列的线程池, * 对于固定大小的线程池,不存在线程数量的动态变化,缺点是当任务提交非常频繁的时候,该队列可能迅速膨胀, * 从而耗尽系统资源。 * @param nThreads 允许线程池存在的线程数 * @return 线程池对象 */ public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } /** * 是newFixedThreadPool方法的一种退化,只是简单的将线程池线程数量设置为1。 * @return 线程池对象 */ public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } /** * 返回一个corePoolSize为0,maximumPoolSize无穷大的线程池,意味着在没有任务时,该线程池内无线程, * 而当任务被提交时,该线程池会使用空闲的线程执行任务,若无空闲线程,则将任务加入SynchronousQueue队列, * 而且SynchronousQueue是直接提交的队列,它总是迫使线程池增加新的线程执行任务。 * 当任务执行完毕,由于corePoolSize为0,因此空闲线程又会在指定时间内(60秒)被回收 * @return */ public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
ThreadPoolExecutor构造函数代码
/** * ThreadPoolExecutor构造函数 * * @param corePoolSize 指定了线程池中的线程数量 * @param maximumPoolSize 指定了线程池中的最大线程数量 * @param keepAliveTime 当线程池数量超过corePoolSize时,多余的空闲线程的存活时间。 * 即,超过corePoolSize的空闲线程,在多长时间内,会被销毁。 * @param unit keepAliveTime的单位 * @param workQueue 任务队列,被提交但尚未被执行的任务 * @param threadFactory 线程工厂,用于创建线程,一般用默认即可 * @param handler 拒绝策略。当任务太多来不及处理,如何拒绝任务。 */ 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; }
ThreadPoolExecutor任务调度逻辑
调度逻辑场景说明
1、直接提交的队列SynchronousQueue,没有容量,每一个插入操作都要等待一个删除操作,反之,每一个删除也要等待插入操作。如果使用SynchronousQueue,提交的任务不会真实保存,而是将新任务提交给线程执行。如果没有空闲的线程,则尝试创建新的线程,如果线程数量已经达到最大值,则执行拒绝策略。因此使用该队列,通常需要设置很大的maximumPoolSize,,否则容易触发拒绝策略。
2、有界的任务队列:如ArrayBlockingQueue队列。构造函数必须带一个容量参数,表示最大容量。当使用有界任务队列,若有新的任务需要执行,如果线程池的实际线程数小于corePoolSize,则优先创建新的线程,若大于corePoolSize,则会将新任务加入等待队列。若等待队列已满,无法加入,则在总线程数不大于maximumPoolSize的前提下,创建新的任务执行任务。若大于maximumPoolSize,则执行拒绝策略。
3、无界的任务队列:如LinkedBlockingQueue类。与有界队列相比,除非系统资源耗尽,否则不会存在提交到等待队列失败的情况。当有新的任务到来,系统的线程数小于corePoolSize时,线程池会生成新的线程执行任务,但当线程池的线程数达到corePoolSize后,就不会继续增加。若后续仍有新的任务加入,而又没有空闲的线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。
4、优先任务队列:优先任务队列是带有执行优先级的队列。它可以通过PriorityBlockingQueue实现,可以控制任务的执行先后顺序。它是一个特殊的无界队列。ArrayBlockingQueue、LinkedBlockingQueue都是按照先进先出算法处理任务的。而PriorityBlockingQueue可以根据任务自身的优先级顺序先后执行,在确保系统性能的同时,也能有很好的质量保证。
ThreadPoolExecutor线程池的核心调度代码
/** * 代码第9行的workerCountOf()函数取得了当前线程池的线程总数。当线程池总数小于corePoolSize核心线程数时, * 会将任务通过addWorker()直接调度执行。否则在第16行代码处(workQueue.offer())加入等待队列。 * 如果进入等待队列失败(如有界队列达到上限或者使用了SynchronousQueue),则会执行第25行,将任务直接提交给线程池。 * 如果当前线程池已经达到maximumPoolSize,则提交失败,执行第27行的拒绝策略。 * * * @param command */ public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); }
线程池的拒绝策略
ThreadPoolExecutor构造函数最后一个参数是拒绝策略.。是系统超负荷运行时的补救措施,也就是线程池中的线程已经用完了,无法继续为新任务服务,同时等待队列中也已经排满了,无法加入新人物了,需要提供一套机制。
JDK内置的拒绝策略
- AbortPolicy策略:该策会直接抛出异常,阻止系统正常工作。
- CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是。任务提交线程的性能极有可能会急剧下降。
- DiscardOldestPolicy策略:该策略将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
- DiscardPolicy策略:该策略默默地丢弃无法处理的任务,不予任务处理。如果允许任务丢失,这种方案最好。
/** * A handler for rejected tasks that throws a * {@code RejectedExecutionException}. */ public static class AbortPolicy implements RejectedExecutionHandler { /** * Creates an {@code AbortPolicy}. */ public AbortPolicy() { } /** * Always throws RejectedExecutionException. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task * @throws RejectedExecutionException always. */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } } /** * A handler for rejected tasks that runs the rejected task * directly in the calling thread of the {@code execute} method, * unless the executor has been shut down, in which case the task * is discarded. */ public static class CallerRunsPolicy implements RejectedExecutionHandler { /** * Creates a {@code CallerRunsPolicy}. */ public CallerRunsPolicy() { } /** * Executes task r in the caller's thread, unless the executor * has been shut down, in which case the task is discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } } /** * A handler for rejected tasks that discards the oldest unhandled * request and then retries {@code execute}, unless the executor * is shut down, in which case the task is discarded. */ public static class DiscardOldestPolicy implements RejectedExecutionHandler { /** * Creates a {@code DiscardOldestPolicy} for the given executor. */ public DiscardOldestPolicy() { } public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } } /** * A handler for rejected tasks that silently discards the * rejected task. */ public static class DiscardPolicy implements RejectedExecutionHandler { /** * Creates a {@code DiscardPolicy}. */ public DiscardPolicy() { } /** * Does nothing, which has the effect of discarding task r. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } }
注:以上内置策略实现了RejectedExecutionHandler接口,若还是无法满足实际应用需要,可以通过实现RejectedExecutionHandler接口进行扩展
自定义策略代码
package com.dsd.jdk.executor.policy; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 自定义线程池和拒绝策略使用例子 * @author daishengda * */ public class RejectThreadPoolDemo { public static class MyTask implements Runnable{ @Override public void run() { System.out.println(System.currentTimeMillis()+":Thread ID:"+Thread.currentThread().getId()); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { MyTask task = new MyTask(); ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10), Executors.defaultThreadFactory(), new RejectedExecutionHandler() { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println(r.toString()+" is discard"); } } ); for (int i = 0; i < Integer.MAX_VALUE; i++) { es.submit(task); TimeUnit.MILLISECONDS.sleep(10); } } }
ThreadFactory-ThreadPoolExecutor参数
用途:是一个接口,方法有Thread newThread(Runnable r),用来创建线程。
JDK默认使用的实现类DefaultThreadFactory
/** * The default thread factory */ static class DefaultThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; DefaultThreadFactory() { SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (t.isDaemon()) t.setDaemon(false); if (t.getPriority() != Thread.NORM_PRIORITY) t.setPriority(Thread.NORM_PRIORITY); return t; } }
扩展线程池
ThreadPoolExecutor提供了beforeExecute()、afterExecute()和terminated()三个接口对线程池进行扩展
beforeExecute():运行任务前执行方法。
afterExecute():运行任务结束后执行方法。
terminated():线程池退出执行方法。
package com.dsd.jdk.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 通过复写线程池ThreadPoolExecutor的三个方法进行扩展, * 对线程池运行状态的跟踪,输入一些有用调试信息,方便定位问题 * @author daishengda * */ public class ExtendThreadPool { public static class MyTask implements Runnable{ public String name; public MyTask(String name) { this.name = name; } @Override public void run() { System.out.println("正在执行"+":Thread ID:"+Thread.currentThread().getId()+ ",Task Name="+name); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { ExecutorService es = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()){ @Override protected void beforeExecute(Thread t, Runnable r) { System.out.println("准备执行:"+((MyTask)r).name); } @Override protected void afterExecute(Runnable r, Throwable t) { System.out.println("执行完成:"+((MyTask)r).name); } @Override protected void terminated() { System.out.println("线程池退出"); } }; for (int i = 0; i < 5; i++) { MyTask task = new MyTask("TASK-GEYM-"+i); es.execute(task); TimeUnit.MILLISECONDS.sleep(10); } /** * 关闭线程池,比较安全优雅的关闭方法,它会等待所有任务执行完成后,再关闭线程池,但它并不会等待所有线程执行完成后再返回 * 因此,可以简单理解为只是发送了一个关闭信号而已。shutdown()执行后,这个线程池就不能再接收其他新的任务了。 */ es.shutdown(); } }