ThreadPoolTaskExecutor原理、详解及案例

为什么要用线程池?

服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大的。

构建服务器应用程序的一个过于简单的模型应该是:每当一个请求到达就创建一个新线程,然后在新线程中为请求服务。实际上,对于原型开发这种方法工作得很好,但如果试图部署以这种方式运行的服务器应用程序,那么这种方法的严重不足就很明显。

每个请求对应一个线程(thread-per-request)方法的不足之一是:为每个请求创建一个新线程的开销很大;为每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源要比花在处理实际的用户请求的时间和资源更多。除了创建和销毁线程的开销之外,活动的线程也消耗系统资源(线程的生命周期!)。在一个JVM 里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目。

线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。

使用线程池的风险

虽然线程池是构建多线程应用程序的强大机制,但使用它并不是没有风险的。用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足,并发错误,线程泄漏,请求过载。

有效使用线程池的准则

(1)不要对那些同步等待其它任务结果的任务排队。这可能会导致上面所描述的那种形式的死锁,在那种死锁中,所有线程都被一些任务所占用,这些任务依次等待排队任务的结果,而这些任务又无法执行,因为所有的线程都很忙。

(2)在为时间可能很长的操作使用合用的线程时要小心。如果程序必须等待诸如 I/O 完成这样的某个资源,那么请指定最长的等待时间,以及随后是失效还是将任务重新排队以便稍后执行。这样做保证了:通过将某个线程释放给某个可能成功完成的任务,从而将最终取得 某些 进展。

理解任务。要有效地调整线程池大小,您需要理解正在排队的任务以及它们正在做什么。它们是 CPU 限制的(CPU-bound)吗?它们是 I/O 限制的(I/O-bound)吗?您的答案将影响您如何调整应用程序。如果您有不同的任务类,这些类有着截然不同的特征,那么为不同任务类设置多个工作队 列可能会有意义,这样可以相应地调整每个池。

线程池的创建

我们可以通过java.util.concurrent.ThreadPoolExecutor来创建一个线程池。

常用构造方法为:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler)

  • corePoolSize: 线程池维护线程的最少数量
  • maximumPoolSize:线程池维护线程的最大数量
  • keepAliveTime: 线程池维护线程所允许的空闲时间
  • unit: 线程池维护线程所允许的空闲时间的单位
  • workQueue: 线程池所使用的缓冲队列
  • handler: 线程池对拒绝任务的处理策略

处理任务的优先级

线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
线程数量达到了corePools,则将任务移入队列等待
队列已满,新建线程(非核心线程)执行任务
队列已满,总线程数又达到了maximumPoolSize,就会抛出异常

扫描二维码关注公众号,回复: 5740716 查看本文章

handler有四个选择

ThreadPoolExecutor.AbortPolicy() :抛出java.util.concurrent.RejectedExecutionException异常
ThreadPoolExecutor.CallerRunsPolicy() : 重试添加当前的任务,他会自动重复调用execute()方法
ThreadPoolExecutor.DiscardOldestPolicy() : 抛弃旧的任务
ThreadPoolExecutor.DiscardPolicy() : 抛弃当前的任务

ThreadPoolTaskExecutor

四个属性在org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor里面都是传递给了java.util.concurrent.ThreadPoolExecutor 所以基本上线程的创建、任务的提交、任务的执行、线程的销毁都是ThreadPoolExecutor来做的

ThreadPoolTaskExecutor 继承图

ThreadPoolTaskExecutor 使用

1. ThreadPoolTaskExecutor配置

复制代码
 1 <!-- spring thread pool executor -->           
 2     <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">  3 <!-- 线程池维护线程的最少数量 -->  4 <property name="corePoolSize" value="5" />  5 <!-- 允许的空闲时间 -->  6 <property name="keepAliveSeconds" value="200" />  7 <!-- 线程池维护线程的最大数量 -->  8 <property name="maxPoolSize" value="10" />  9 <!-- 缓存队列 --> 10 <property name="queueCapacity" value="20" /> 11 <!-- 对拒绝task的处理策略 --> 12 <property name="rejectedExecutionHandler"> 13 <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /> 14 </property> 15 </bean>
复制代码

属性字段说明

corePoolSize:线程池维护线程的最少数量

keepAliveSeconds:允许的空闲时间

maxPoolSize:线程池维护线程的最大数量

queueCapacity:缓存队列

rejectedExecutionHandler:对拒绝task的处理策略

2. execute(Runable)方法执行过程

如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。

如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maxPoolSize,建新的线程来处理被添加的任务。

如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maxPoolSize,那么通过handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程 maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

当线程池中的线程数量大于corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。

3. 示例代码

Junit Test

复制代码
 1 @RunWith(SpringJUnit4ClassRunner.class)
 2 @ContextConfiguration(classes = { MultiThreadConfig.class })  3 public class MultiThreadTest {  4  5  @Autowired  6 private ThreadPoolTaskExecutor taskExecutor;  7  8  @Autowired  9 private MultiThreadProcessService multiThreadProcessService; 10 11  @Test 12 public void test() { 13 14 int n = 20; 15 for (int i = 0; i < n; i++) { 16 taskExecutor.execute(new MultiThreadDemo(multiThreadProcessService)); 17 System.out.println("int i is " + i + ", now threadpool active threads totalnum is " + taskExecutor.getActiveCount()); 18  } 19 20 try { 21  System.in.read(); 22 } catch (IOException e) { 23 throw new RuntimeException(e); 24  } 25  } 26 }
复制代码

MultiThreadDemo

复制代码
 1 /**
 2  * 多线程并发处理demo
 3  * @author daniel.zhao  4  *  5 */  6 public class MultiThreadDemo implements Runnable {  7  8 private MultiThreadProcessService multiThreadProcessService;  9 10 public MultiThreadDemo() { 11  } 12 13 public MultiThreadDemo(MultiThreadProcessService multiThreadProcessService) { 14 this.multiThreadProcessService = multiThreadProcessService; 15  } 16 17  @Override 18 public void run() { 19  multiThreadProcessService.processSomething(); 20  } 21 22 }
复制代码

MultiThreadProcessService

复制代码
 1 @Service
 2 public class MultiThreadProcessService {  3  4 public static final Logger logger = Logger.getLogger(MultiThreadProcessService.class);  5  6 /**  7  * 默认处理流程耗时1000ms  8 */  9 public void processSomething() { 10 logger.debug("MultiThreadProcessService-processSomething" + Thread.currentThread() + "......start"); 11 try { 12 Thread.sleep(1000); 13 } catch (InterruptedException e) { 14 throw new RuntimeException(e); 15  } 16 logger.debug("MultiThreadProcessService-processSomething" + Thread.currentThread() + "......end"); 17  } 18 }
复制代码

MultiThreadConfig

1 @Configuration
2 @ComponentScan(basePackages = { "com.xxx.multithread" })
3 @ImportResource(value = { "classpath:config/application-task.xml" }) 4 @EnableScheduling 5 public class MultiThreadConfig { 6 }

参考:

  https://blog.csdn.net/foreverling/article/details/78073105

猜你喜欢

转载自www.cnblogs.com/pansw/p/10639055.html