`
marb
  • 浏览: 422245 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

ThreadPoolExecutor线程池的使用与理解

    博客分类:
  • JAVA
 
阅读更多

线程池的作用就是用尽可能少的线程来执行尽可能多的Runnable,以实现对线程的充分利用。

从ThreadPoolExecutor类的构造方法说起:

ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,    // 核心线程数
                          int maximumPoolSize, // 最大线程数
                          long keepAliveTime,  // 生存时间
                          TimeUnit unit,       // keepAliveTime值的度量单位
                          BlockingQueue<Runnable> workQueue)   // 阻塞队列

我们通过ThreadPoolExecutor类的execute()方法添加我们要执行的任务。下面通过实例讲解ThreadPoolExecutor类的使用。

任务类Task:将会被线程池执行的任务类

class Task {
	public void fun() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + " 执行 - " + i);
		}
	}
}

在创建线程池之前,我们先了解一下BlockingQueue,在构造线程池的时候,需要为其指定一个BlockingQueue,可用于传输和保持提交的任务。可以使用此队列与池大小进行交互。

BlockingQueue(阻塞队列):
  如果运行的线程少于 corePoolSize,则 Executor始终首选添加新的线程,而不进行排队(不添加进阻塞队列)。
  如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
  如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

排队有三种通用策略:

1:直接提交
SynchronousQueue
它将任务直接提交给线程而不保存它们。在此,如果不存在可用于立即运行任务的线程,
则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。
直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。
当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 
SynchronousQueue线程安全的Queue,可以存放若干任务(但当前只允许有且只有一个任务在等待),
其中每个插入操作必须等待另一个线程的对应移除操作,也就是说A任务进入队列,B任务必须等A任务被移除之后才能进入队列,否则执行异常策略。
  
你来一个我扔一个,所以说SynchronousQueue没有任何内部容量。
  
比如:核心线程数为2,最大线程数为3;使用SynchronousQueue。
当前有2个核心线程在运行,又来了个A任务,两个核心线程没有执行完当前任务,根据如果运行的线程等于或多于 corePoolSize,
则 Executor 始终首选将请求加入队列,而不添加新的线程。所以A任务被添加到队列,此时的队列是SynchronousQueue,
当前不存在可用于立即运行任务的线程,因此会构造一个新的线程,此时又来了个B任务,两个核心线程还没有执行完。
新创建的线程正在执行A任务,所以B任务进入Queue后,最大线程数为3,发现没地方仍了。就只能执行异常策略(RejectedExecutionException)。
代码示例:

public class PoolExecutorTest {
	public static void main(String[] args) {
		final Task task = new Task();
		
		ThreadPoolExecutor pool = 
			new ThreadPoolExecutor(2, 3, 3L, 
					TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadPoolExecutor.AbortPolicy());
		for (int i = 0; i < 4; i++) {
			pool.execute(new Runnable() {
				@Override
				public void run() {
					task.fun();
					System.out.println("run");
				}
			});
		}
		pool.shutdown();
	}
}
// 在添加第4个任务的时候,程序抛出 java.util.concurrent.RejectedExecutionException


2:无界队列如LinkedBlockingQueue
使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有核心线程都在忙时新任务在队列中等待。
这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就没意义了。)也就不会有新线程被创建,都在那等着排队呢。
如果未指定容量,则它等于 Integer.MAX_VALUE。
如果设置了Queue预定义容量,则当核心线程忙碌时,新任务会在队列中等待,直到超过预定义容量(新任务没地方放了),才会执行异常策略。
  
你来一个我接一个,直到我容不下你了。FIFO,先进先出。
  
比如:核心线程数为2,最大线程数为3;使用LinkedBlockingQueue(1),设置容量为1。
当前有2个核心线程在运行,又来了个A任务,两个核心线程没有执行完当前任务,根据如果运行的线程等于或多于 corePoolSize,
则 Executor 始终首选将请求加入队列,而不添加新的线程。所以A任务被添加到队列,此时的队列是LinkedBlockingQueue,
此时又来了个B任务,两个核心线程没有执行完当前任务,A任务在队列中等待,队列已满。所以根据如果无法将请求加入队列,则创建新的线程,
B任务被新创建的线程所执行,此时又来个C任务,此时maximumPoolSize已满,队列已满,只能执行异常策略(RejectedExecutionException)。
代码示例:

public class PoolExecutorTest {
	public static void main(String[] args) {
		final Task task = new Task();
		
		ThreadPoolExecutor pool = 
			new ThreadPoolExecutor(2, 3, 3L, 
					TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1), new ThreadPoolExecutor.AbortPolicy());
		for (int i = 0; i < 5; i++) {
			pool.execute(new Runnable() {
				@Override
				public void run() {
					task.fun();
					System.out.println("run");
				}
			});
		}
		pool.shutdown();
	}
}
// 在添加第5个任务的时候,程序抛出 java.util.concurrent.RejectedExecutionException

无界的话要防止任务增长的速度远远超过处理任务的速度。

3:有界队列如ArrayBlockingQueue 
操作模式跟LinkedBlockingQueue查不多,只不过必须为其设置容量。所以叫有界队列。
new ArrayBlockingQueue<Runnable>(Integer.MAX_VALUE) 跟 new LinkedBlockingQueue(Integer.MAX_VALUE)效果一样。
LinkedBlockingQueue 底层是链表结构
ArrayBlockingQueue  底层是数组结构
  
你来一个我接一个,直到我容不下你了。FIFO,先进先出。

代码示例

public class PoolExecutorTest {
	public static void main(String[] args) {
		final Task task = new Task();
		
		ThreadPoolExecutor pool = 
			new ThreadPoolExecutor(2, 3, 3L, 
					TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1), new ThreadPoolExecutor.AbortPolicy());
		for (int i = 0; i < 5; i++) {
			pool.execute(new Runnable() {
				@Override
				public void run() {
					task.fun();
					System.out.println("run");
				}
			});
		}
		pool.shutdown();
	}
}
// 在添加第5个任务的时候,程序抛出 java.util.concurrent.RejectedExecutionException



关于keepAliveTime:

JDK解释:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
也就是说啊,线程池中当前的空闲线程服务完某任务后的存活时间。如果时间足够长,那么可能会服务其它任务。
这里的空闲线程包不包括后来新创建的服务线程呢?我的理解是包括的。

关于handler有四个选择:

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

小的分析:

使用无界队列,要防止任务增长的速度远远超过处理任务的速度,控制不好可能导致的结果就是内存溢出。
使用有界队列,关键在于调节线程数和Queue大小 ,线程数多,队列容量少,资源浪费。线程数少,队列容量多,性能低,还可能导致内存溢出。

分享到:
评论

相关推荐

    Java线程池与ThreadPoolExecutor.pdf

    Java线程池是Java并发编程中...总结来说,理解并正确使用Java线程池和ThreadPoolExecutor对于优化Java应用程序的并发性能至关重要。通过调整线程池的参数,可以平衡资源利用率和系统响应时间,从而提高整体的系统效率。

    使用线程池ThreadPoolExecutor 抓取论坛帖子列表

    在IT行业中,线程池是多线程编程中一个重要的概念,它可以帮助我们高效地管理和控制并发执行的任务。...通过深入理解线程池的工作机制和源码,我们可以更好地设计和优化我们的并发程序,实现高效的数据抓取。

    线程池使用Demo

    1. `ThreadPoolExecutor`:这是最通用的线程池实现,允许自定义工作队列、线程工厂和拒绝策略。 2. `FixedThreadPool`:固定大小的线程池,线程数量不变,多余的提交任务会被放入队列等待。 3. `...

    线程池使用示例(含源代码)

    在提供的"线程池示例"文件中,应该包含了创建和使用线程池的代码示例,你可以参考并进行扩展,例如添加更多任务、调整线程池参数,或者实现自定义的线程工厂和拒绝策略,以加深对线程池的理解。

    线程池ThreadPoolExecutor原理源码分析.md

    线程池作为 Java 并发编程中的重要组件,在实际应用中被广泛使用。其核心类 `ThreadPoolExecutor` 实现了对线程的管理、调度等功能。本文将围绕 `ThreadPoolExecutor` 的核心方法 `execute()` 进行深入解析,帮助...

    Android开发中线程池的使用Demo

    在"Android开发中线程池的使用Demo"博客中,可能还会介绍如何使用`Future`和`Callable`来获取任务执行结果,以及如何通过`ThreadPoolExecutor`的其他方法(如`getActiveCount()`和`getCompletedTaskCount()`)监控...

    一个简单的线程池例子

    在Python中,`concurrent.futures`模块提供了线程池的实现,如`ThreadPoolExecutor`,使用方式类似于Java,通过`submit()`方法提交任务,并可选择性地获取`Future`对象以获取执行结果。 线程池的例子代码通常包括...

    安卓,线程池的使用 ,封装

    线程池是由`java.util.concurrent.ThreadPoolExecutor`类提供的,它允许我们预先创建一定数量的线程,而不是为每个任务创建新的线程。这样可以避免频繁创建和销毁线程带来的开销,同时能够更好地控制系统的资源利用...

    java 四种线程池实例

    总之,Java线程池提供了一种强大的工具来管理和优化并发任务的执行,理解并熟练使用各种线程池实例能够显著提升程序的效率和可维护性。在设计系统时,应该充分考虑线程池的选择和配置,以适应不同类型的异步任务需求...

    简单线程池与线程池检查的实现

    在标签“源码”和“工具”的提示下,我们可以推测该话题可能涉及到线程池的具体实现代码,例如使用Java的`java.util.concurrent.ThreadPoolExecutor`类进行线程池的创建和管理,以及可能通过自定义工具或框架对...

    JAVA使用线程池查询大批量数据

    首先,理解线程池的概念至关重要。线程池是一种多线程处理形式,预先创建了若干个线程,当有任务需要执行时,会从线程池中取出一个线程来执行任务,任务执行完毕后,线程返回线程池中等待新的任务。这种机制避免了...

    Java线程池:深入理解其工作原理与高效运用

    线程池是Java并发编程中的一个重要组成部分,它通过减少线程的创建和销毁来提高程序性能,同时提供了一种有效的方式来...理解线程池的工作原理和合理配置线程池参数,可以帮助开发者编写出更高效、更稳定的并发程序。

    Java ThreadPoolExecutor的参数深入理解

    ThreadPoolExecutor的参数深入理解是Java开发人员需要掌握的重要知识点,本文将对ThreadPoolExecutor的参数进行深入理解,帮助读者更好地掌握ThreadPoolExecutor的使用。 一、ThreadPoolExecutor的参数 ...

    线程池java写的代码

    线程池是Java多线程编程中的一个重要概念,它是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。...理解并正确使用线程池对于提升程序性能和优化多线程应用至关重要。

    Tomcat线程池实现简介

    `AprEndpoint`使用APR库提供的线程池,而`PoolTcpEndpoint`则采用内部的`ThreadPool`。`PoolTcpEndpoint`通过`LeaderFollowerWorkerThread`实例接收并处理来自客户端的连接,这种设计能有效提升并发处理能力,减少...

    java线程池的使用方式

    ### Java线程池的使用方式 #### 一、简介 线程在Java中扮演着至关重要的角色。在早期的JDK版本(如JDK 1.4及之前)中,线程池的功能相对简单,使用起来不够灵活。然而,自JDK 1.5开始,随着`java.util.concurrent`...

    火山安卓编程线程池例子

    在Android中,可以使用`java.util.concurrent`包下的`ExecutorService`、`ThreadPoolExecutor`等类来创建线程池。线程池的主要优点包括: 1. **资源管理**:线程池可以有效地管理线程,避免过多线程消耗系统资源。 ...

    线程池demo

    线程池在软件开发中扮演着重要的角色,尤其是在性能优化和资源管理方面。在Android系统中,线程池的应用尤其广泛,因为...在实际项目中,还需要考虑线程池大小与设备性能、任务性质等因素的匹配,以达到最佳效果。

    用C ++ 11和Folly实现的简单线程池(facebook C ++基础库).zip

    2. **Folly库**:可能使用了Folly中的`FiberManager`或者`ThreadPoolExecutor`来管理线程池。`FiberManager`提供了一种轻量级的协程实现,可以实现线程内的并发。`ThreadPoolExecutor`则是一个预创建线程的池,用于...

    Java中的线程与线程池.pptx

    总之,理解Java中的线程和线程池原理,以及如何正确使用它们,对于编写高性能、高并发的Java应用程序至关重要。通过合理配置和管理线程池,我们可以实现更有效的资源利用,避免潜在的性能瓶颈和系统异常。

Global site tag (gtag.js) - Google Analytics