如何处理线程池的并发?

【前言】我们从事Android开发以来,都自始自终被灌输着处理耗时的任务时要在非UI线程做。于是我们有了各种处理并发的编程手段,无论是自己用new Thread(Runnable)新起工作线程(Worker thread),还是利用Android提供的API(AsnyTask,CursorLaoder等)都是处理耗时任务的解决方案。但是在一个大型的应用程序中,如果我们需要处理数量很多且频繁的耗时任务时,如果还是采用之前的手段,无疑会带来很多不便;一来频繁创建销毁线程会造成资源(内存和Cpu)的浪费,二来代码会显得很凌乱。于是我们提出了使用线程池来处理频繁的耗时任务。

在JDK1.5的类库中,JAVA的开发者就给我们提供了现成的线程池,java.util.concurrent包下就提供了线程池的api。

我的应用架构设计中,在处理并发的任务时,就要用到线程池,关于线程池,我把它想成是一个统一管理多线程任务的地方,具体来说,就是线程池接受多线程任务(实现Runnable接口),并通过创建线程池时配置好的一些参数来统一管理和执行这些任务,我们在需要执行耗时任务的时候,只需要向线程池发送一个消息(希望执行的耗时操作)就可以了,当然,我们在发送请求的同时,可以传入一些回调的接口,这样就能在耗时任务执行完毕之后得到回调来更新UI。

如何实现自定义的线程池?

JDK提供的线程池固然可以满足一般的开发需求,但是实现自定义的线程池还是很有必要的。

自定义线程池一般可以继承自类ThreadPoolExcutor,该类的构造方法里面有几个很重要的参数,这几个参数决定着线程池的策略和处理能力。

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

corePoolSize - 池中所保存的线程数,包括空闲线程。

maximumPoolSize - 池中允许的最大线程数。

keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

unit - keepAliveTime 参数的时间单位。

workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。

threadFactory - 执行程序创建新线程时使用的工厂。

handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

这些参数既可以在构造的时候传入,也可以在后期通过set方法来设定。

setMaximumPoolSize(int maximumPoolSize)

setRejectedExecutionHandler(RejectedExecutionHandler handler)

setKeepAliveTime(long time, TimeUnit unit)

setCorePoolSize(int corePoolSize)

线程池表达了一种边界的概念,这个边界是通过corePoolSize和maximumPoolSize来体现的,如果线程池接受的任务小于corePoolSize,则新提交的任务会被线程池通过新建线程来处理,如果大于corePoolSize而小于maximumPoolSize,则新传入的任务会被排队队列容纳,如果队伍容纳不下或者队列是直接提交型队列时,任务就会被提交给线程池,线程池会创建新线程来处理任务,如果大于maximumPoolSize,则新加入的任务会被拒绝,这里要注意的时,被拒绝的意思并不等于被抛弃,只是会触发线程池的RejectExecutionHandler,这里我们可以通过设置自定义的handler来实现线程池的拒绝策略(比如用一个队列来保存被拒绝的任务,以后还可以从该队列取出任务继续执行)。

在自定义线程池的时候,选择合适的排队队列显得尤为重要,我们一般有以下三种队列可选

SynchronousQueue 直接提交队列,该队列不会保存任务,而是会把任务直接提交给线程池。

LinkedBlockingQueue 无界队列,这队列没有极限,可以无限加入任务保存,这样maximumPoolSize就不起作用了,创建的线程数就永远不会超过corePoolSize了。

ArrayBlockingQueue 有界队列。

如何处理被拒绝任务?

1.定义被拒绝的策略

当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被拒绝。在以上两种情况下,execute 方法都将调用其RejectedExecutionHandler 的RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。

ThreadPoolExecutor.AbortPolicy

用于被拒绝任务的处理程序,它将抛出 RejectedExecutionException.

ThreadPoolExecutor.DiscardOldestPolicy

用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。

ThreadPoolExecutor.DiscardPolicy

用于被拒绝任务的处理程序,默认情况下它将放弃被拒绝的任务。

2.自定义RejectedExecutionHandler

使用自定义RejectedExecutionHandler来定义线程池的拒绝策略,比如用一个队列来保存被拒绝的任务以便以后继续执行或者直接丢弃。

更多灵活的处理

ThreadPoolExcutor提供了一些可被覆写的hook方法

afterExecute(Runnable r,Throwable t)

基于完成执行给定 Runnable 所调用的方法。

beforeExecute(Thread t,Runnable r)

在执行给定线程中的给定 Runnable 之前调用的方法。

这两个方法在每个任务被线程池执行前后都被会回调,所以我们可以覆写这两个方法来自定义需要的功能。

时间: 2024-10-17 00:32:40

如何处理线程池的并发?的相关文章

控制每次线程池的并发线程的最大个数

[本人原创],欢迎交流和分形技术,转载请附上如下内容: 作者:itshare [转自]http://www.cnblogs.com/itshare/ 1. 实验目的:       使用线程池的时候,有时候需要考虑服务器的最大线程数目和程序最快执行所有业务逻辑的取舍.并非逻辑线程越多也好,而且新的逻辑线程必须会在线程池的等待队列中等待 ,直到线程池中工作的线程执行完毕,才会有系统线程取出等待队列中的逻辑线程,进行CPU运算. 2.  解决问题:     <a>如果不考虑服务器实际可支持的最大并行

高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

(1)高并发.任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换(2)并发不高.任务执行时间长的业务要区分开看:a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换(3)并发高.业务执行时间长,解决这种类型任务

python 之 线程池实现并发

使用线程池实现高IO并发 模块:ThreadPoolExecutor, as_completed 测试代码如下: #!/opt/python3/bin/python3 from concurrent.futures import ThreadPoolExecutor, as_completed import time def test(arg1, arg2, arg3): time.sleep(int(arg1)) print('参数1:%s 参数2:%s 参数3:%s' % (arg1,arg

java线程池处理并发业务

/** * 缓存对象 map */ public static CachePool<String, Object> mapPool = CachePool.getInstance(); private static final int NTHREADS=5; // 使用线程池来避免 为每个请求创建一个线程. private static final Executor threadPool=Executors.newFixedThreadPool(NTHREADS); public Financ

java线程池模拟并发

public class CountDownLatchTest1 implements Runnable{ final AtomicInteger number = new AtomicInteger(); volatile boolean bol = false; @Override public void run() { System.out.println(number.getAndIncrement()); synchronized (this) { try { if (!bol) {

Java并发(四)线程池监控

目录 一.线程池监控参数 二.线程池监控类 三.注意事项 在上一篇博文中,我们介绍了线程池的基本原理和使用方法.了解了基本概念之后,我们可以使用 Executors 类创建线程池来执行大量的任务,使用线程池的并发特性提高系统的吞吐量.但是,线程池使用不当也会使服务器资源枯竭,导致异常情况的发生,比如固定线程池的阻塞队列任务数量过多.缓存线程池创建的线程过多导致内存溢出.系统假死等问题.因此,我们需要一种简单的监控方案来监控线程池的使用情况,比如完成任务数量.未完成任务数量.线程大小等信息. 一.

Python并发编程05/ 死锁/递归锁/信号量/GIL锁/进程池/线程池

目录 Python并发编程05/ 死锁/递归锁/信号量/GIL锁/进程池/线程池 1.昨日回顾 2.死锁现象与递归锁 2.1死锁现象 2.2递归锁 3.信号量 4.GIL全局解释器锁 4.1背景 4.2为什么加锁 5.GIL与Lock锁的区别 6.验证计算密集型IO密集型的效率 6.1 IO密集型 6.2 计算密集型 7.多线程实现socket通信 7.1服务端 7.2客户端 8.进程池,线程池 Python并发编程05/ 死锁/递归锁/信号量/GIL锁/进程池/线程池 1.昨日回顾 #生产者消

线程池的选择

高并发.任务执行时间短的业务怎样使用线程池?并发不高.任务执行时间长的业务怎样使用线程池?并发高.业务执行时间长的业务怎样使用线程池? 1)高并发.任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换 (2)并发不高.任务执行时间长的业务要区分开看: a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务 b)假如是业务时间长集中在计算操作上,也就是计算

Java线程池入门

序 为什么要用线程池?什么情况下才会用到线程池? 并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 因此,就用到了线程池:线程池中的线程可以复用,就是执行完一个任务,并不被销毁,而是继续执行下一个任务. 如下使用线程: public class Test{      public static void main(String[] args) {         long start = System