Java基础——线程池
1、线程池
线程池是为了避免线程频繁的创建和销毁带来的性能消耗,而建立的一种池化技术,它是把已创建的线程放入“池”中,当有任务来临时就可以重用已有的线程,无需等待创建的过程,这样就可以有效提高程序的响应速度
阿里巴巴的《Java 开发手册》中是这样规定线程池的:线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的读者更加明确线程池的运行规则,规避资源耗尽的风险
Executors 返回的线程池对象的弊端:
FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM(内存用完)
CachedThreadPool 和 ScheduledThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM(内存用完)
Executors 的源码中Executors.newFixedThreadPool()、Executors.newSingleThreadExecutor() 和 Executors.newCachedThreadPool() 等方法的底层都是通过 ThreadPoolExecutor 实现的
//创建一个可控最大并发数线程池,以共享的无界队列方式来运行这些线程(只有要请求的过来,就会在一个队列里等待执行) public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } //创建单线程化线程池 public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } //创建可回收缓存线程池 public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } //创建支持定时与周期性任务的线程池 public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { return new DelegatedScheduledExecutorService (new ScheduledThreadPoolExecutor(1, threadFactory)); }
ThreadPoolExecutor方法
1 public ThreadPoolExecutor(int corePoolSize, //线程池的常驻核心线程数。如果设置为 0,则表示在没有任何任务时,销毁线程池;如果大于 0,即使没有任务时也会保证线程池的线程数量等于此值。但需要注意,此值如果设置的比较小,则会频繁的创建和销毁线程(创建和销毁的原因会在本课时的下半部分讲到);如果设置的比较大,则会浪费系统资源,所以开发者需要根据自己的实际业务来调整此值 2 int maximumPoolSize, //线程池在任务最多时,最大可以创建的线程数。官方规定此值必须大于 0,也必须大于等于 corePoolSize,此值只有在任务比较多,且不能存放在任务队列时,才会用到 3 long keepAliveTime, //线程的存活时间,当线程池空闲时并且超过了此时间,多余的线程就会销毁,直到线程池中的线程数量销毁的等于 corePoolSize 为止,如果 maximumPoolSize 等于 corePoolSize,那么线程池在空闲的时候也不会销毁任何线程 4 TimeUnit unit,//存活时间的单位,它是配合 keepAliveTime 参数共同使用的 5 BlockingQueue<Runnable> workQueue,//线程池执行的任务队列,当线程池的所有线程都在处理任务时,如果来了新任务就会缓存到此任务队列中排队等待执行,例:new LinkedBlockingQueue<>(2),最多储存2个任务 6 ThreadFactory threadFactory,//线程的创建工厂,此参数一般用的比较少,我们通常在创建线程池时不指定此参数,它会使用默认的线程创建工厂的方法来创建线程 7 RejectedExecutionHandler handler //指定线程池的拒绝策略,当线程池的任务已经在缓存队列 workQueue 中存储满了之后,并且不能创建新的线程来执行此任务时,就会用到此拒绝策略,它属于一种限流保护的机制 8 ) { 9 if (corePoolSize < 0 || 10 maximumPoolSize <= 0 || 11 maximumPoolSize < corePoolSize || 12 keepAliveTime < 0) 13 throw new IllegalArgumentException(); 14 if (workQueue == null || threadFactory == null || handler == null) 15 throw new NullPointerException(); 16 this.corePoolSize = corePoolSize; 17 this.maximumPoolSize = maximumPoolSize; 18 this.workQueue = workQueue; 19 this.keepAliveTime = unit.toNanos(keepAliveTime); 20 this.threadFactory = threadFactory; 21 this.handler = handler; 22 } 23 //若corePoolSize = 3,LinkedBlockingQueue<>(2),拒绝策略默认,最多可以执行3+2=5个任务,超过这个任务数,抛出异常
2、ThreadPoolExecutor 的执行方法
execute(),用来执行线程池任务,不能接收返回值,属于 Executor 接口的方法
1 public void execute(Runnable command) { 2 if (command == null) 3 throw new NullPointerException(); 4 int c = ctl.get(); 5 // 当前工作的线程数小于核心线程数 6 if (workerCountOf(c) < corePoolSize) { 7 // 创建新的线程执行此任务 8 if (addWorker(command, true)) 9 return; 10 c = ctl.get(); 11 } 12 // 检查线程池是否处于运行状态,如果是则把任务添加到队列 13 if (isRunning(c) && workQueue.offer(command)) { 14 int recheck = ctl.get(); 15 // 再出检查线程池是否处于运行状态,防止在第一次校验通过后线程池关闭 16 // 如果是非运行状态,则将刚加入队列的任务移除 17 if (! isRunning(recheck) && remove(command)) 18 reject(command); 19 // 如果线程池的线程数为 0 时(当 corePoolSize 设置为 0 时会发生) 20 else if (workerCountOf(recheck) == 0) 21 addWorker(null, false); // 新建线程执行任务 22 } 23 // 核心线程都在忙且队列都已爆满,尝试新启动一个线程执行失败 24 //command,线程应首先运行的任务,如果没有则可以设置为 null 25 //false,判断是否可以创建线程的阀值(最大值),如果等于 true 则表示使用 corePoolSize 作为阀值,false 则表示使用 maximumPoolSize 作为阀值 26 else if (!addWorker(command, false)) 27 // 执行拒绝策略 28 reject(command); 29 }
submit(),用来执行线程池任务,可以接收线程池执行的返回值,属于 ExecutorService 接口的方法
1 //submit底层也是由execute()方法来实现的,但是submit()方法可以返回值 2 public Future<?> submit(Runnable task) { 3 if (task == null) throw new NullPointerException(); 4 RunnableFuture<Void> ftask = newTaskFor(task, null); 5 execute(ftask); 6 return ftask; 7 }
3、线程池拒绝策略
当线程池中的任务队列已经被存满,再有任务添加时会先判断当前线程池中的线程数是否大于等于线程池的最大值,如果是,则会触发线程池的拒绝策略
AbortPolicy,终止策略,线程池会抛出异常并终止执行,它是默认的拒绝策略
CallerRunsPolicy,把任务交给当前线程来执行
DiscardPolicy,忽略此任务(最新的任务)
DiscardOldestPolicy,忽略最早的任务(最先加入队列的任务)
//定义线程池时添加拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 10,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(2),
new ThreadPoolExecutor.AbortPolicy()); //AbortPolicy拒绝策略
4、自定义拒绝策略
自定义拒绝策略只需要新建一个 RejectedExecutionHandler 对象,然后重写它的 rejectedExecution() 方法
1 ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 3, 10, 2 TimeUnit.SECONDS, new LinkedBlockingQueue<>(2), 3 new RejectedExecutionHandler() { // 添加自定义拒绝策略 4 @Override 5 public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { 6 // 业务处理方法 7 System.out.println("执行自定义拒绝策略"); 8 } 9 });
5、ThreadPoolExecutor的扩展
ThreadPoolExecutor 的扩展主要是通过重写它的 beforeExecute() 和 afterExecute() 方法实现的,我们可以在扩展方法中添加日志或者实现数据统计