线程池
2016-02-19 17:38
281 查看
回顾:对于线程,有两种实现方式:
第一种是继承Thread类,重写run()方法;[其中Thread类其实也是实现了Runnable接口]
第二种是实现Runnable接口,实现run()方法!
当用户请求来了的时候,就创建一个线程,这样使用起来确实挺方便,那么问题来了,
当请求并发数量很大的时候呢?光是创建和销毁这些线程就足以把内存消耗光而宕机了!
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
这就有了线程池的由来!
线程池可以解决两个不同问题:
由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。
每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。
以下是线程池核心类之间的关系图:类图,通过该图可以很清楚的明白他们之间的关系!!
因为Executors中包含了静态方法newFixedThreadPool等等,所以可以通过它们来创建符合自己需求的线程池!
例如:ExecutorService exe = Executors.newFixedThreadPool(5);
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类!
解释一下下面几个重要的属性:
该类提供了四个构造方法:
仔细看源码的话就会发现,其实有三个构造方法都是最终调用了第四个构造方法,也就是下面这个:
现在来分析各个参数的具体含义:
池中所保存的线程数,包括空闲线程。
在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了 prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即
在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0, 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓 存队列当中;
可能看到上面的参数具体含义很多人还是不太明白,对于多线程,其实可以用下面三点来总结上面参数的表达含义!!!
1. 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
2. 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
3. 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
对于上面所说的BlockingQueue排队序列,其包含有三种通用策略:
1. 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即 运行任 务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求 集时出现锁。直 接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到
达时,此策略允许无界 线程具有增长的可能性。
2. 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务 在队列中 等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全 独立于其他任务, 即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请 求,当命令以超过队列所能 处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调 整和控 制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源 和上下文切换开 销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是
I/O 边界),则系统可能为超过您 许可的更多线程安排时间。 使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也 会降低吞吐量。
线程池状态
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
每次线程去读取volatile修饰的这个变量的时候,都是从内存中读取的最新的值!!
(非volatile类型的64位数值变量【long double】,JVM允许将64位读操作和写操作分解成两个32位的操作,当读取一个long变量时,
如果对该变量的读操作和写操作在不同的线程中执行,那么很可能会取到某个值的高32位和另一个值的低32位)
下面的几个static final变量表示runState可能的几个取值。
当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
对于ThreadPoolExecutor类中实现的几个核心方法,比如execute方法,这里就不再解析,可以通过阅读源码来跟踪其用法!
对于线程池,现在很多框架都已经封装了,比如Spring,在创建方面,这样就简化了线程池的使用!
给个spring创建线程池的示例:
以上
第一种是继承Thread类,重写run()方法;[其中Thread类其实也是实现了Runnable接口]
第二种是实现Runnable接口,实现run()方法!
当用户请求来了的时候,就创建一个线程,这样使用起来确实挺方便,那么问题来了,
当请求并发数量很大的时候呢?光是创建和销毁这些线程就足以把内存消耗光而宕机了!
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
这就有了线程池的由来!
线程池可以解决两个不同问题:
由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行任务集时使用的线程)的方法。
每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。
以下是线程池核心类之间的关系图:类图,通过该图可以很清楚的明白他们之间的关系!!
因为Executors中包含了静态方法newFixedThreadPool等等,所以可以通过它们来创建符合自己需求的线程池!
例如:ExecutorService exe = Executors.newFixedThreadPool(5);
java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类!
解释一下下面几个重要的属性:
private volatile int poolSize; //线程池中当前的线程数 private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数 private long completedTaskCount; //用来记录已经执行完毕的任务个数 private volatile long keepAliveTime; //线程存活时间 private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务
该类提供了四个构造方法:
仔细看源码的话就会发现,其实有三个构造方法都是最终调用了第四个构造方法,也就是下面这个:
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; }
现在来分析各个参数的具体含义:
corePoolSize-
池中所保存的线程数,包括空闲线程。
在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了 prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即
在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0, 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓 存队列当中;
maximumPoolSize- 池中允许的最大线程数,它表示在线程池中最多能创建多少个线程;
keepAliveTime- 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit- keepAliveTime 参数的时间单位。
workQueue- 执行前用于保持任务的队列。此队列仅保持由execute 方法提交的Runnable 任务。它是一个阻塞队列,用来存储等待执行的任务,排队策略跟BlockingQueue有关!在后面讲述!
threadFactory- 执行程序创建新线程时使用的工厂。
handler- 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
可能看到上面的参数具体含义很多人还是不太明白,对于多线程,其实可以用下面三点来总结上面参数的表达含义!!!
1. 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
2. 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
3. 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。
对于上面所说的BlockingQueue排队序列,其包含有三种通用策略:
1. 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即 运行任 务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求 集时出现锁。直 接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到
达时,此策略允许无界 线程具有增长的可能性。
2. 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务 在队列中 等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全 独立于其他任务, 即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请 求,当命令以超过队列所能 处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调 整和控 制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源 和上下文切换开 销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是
I/O 边界),则系统可能为超过您 许可的更多线程安排时间。 使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也 会降低吞吐量。
线程池状态
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
volatile int runState; static final int RUNNING = 0; static final int SHUTDOWN = 1; static final int STOP = 2; static final int TERMINATED = 3;
runState表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
每次线程去读取volatile修饰的这个变量的时候,都是从内存中读取的最新的值!!
(非volatile类型的64位数值变量【long double】,JVM允许将64位读操作和写操作分解成两个32位的操作,当读取一个long变量时,
如果对该变量的读操作和写操作在不同的线程中执行,那么很可能会取到某个值的高32位和另一个值的低32位)
下面的几个static final变量表示runState可能的几个取值。
当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
对于ThreadPoolExecutor类中实现的几个核心方法,比如execute方法,这里就不再解析,可以通过阅读源码来跟踪其用法!
对于线程池,现在很多框架都已经封装了,比如Spring,在创建方面,这样就简化了线程池的使用!
给个spring创建线程池的示例:
<!-- spring创建线程池 --> <bean id="threadPoolExecuter" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor" > <property name="corePoolSize" value="2"/> <property name="maxPoolSize" value="20"/> <property name="queueCapacity" value="200"/> </bean>
以上
相关文章推荐
- C#线程间不能调用剪切板的解决方法
- C#多线程学习之(四)使用线程池进行多线程的自动管理
- C#线程同步的三类情景分析
- C#获取进程或线程相关信息的方法
- C#停止线程的方法
- C#子线程更新UI控件的方法实例总结
- C#线程队列用法实例分析
- C++使用CriticalSection实现线程同步实例
- c++线程池实现方法
- 基于C++实现的线程休眠代码
- C语言实现支持动态拓展和销毁的线程池
- c++实现简单的线程池
- VB读取线程、句柄及写入内存的API代码实例
- C#网络编程基础之进程和线程详解
- C#通过Semaphore类控制线程队列的方法
- C#多线程处理多个队列数据的方法
- C#实现线程安全的简易日志记录方法
- C#中线程同步对象的方法分析
- ASP.NET线程相关配置
- 浅析linux环境下一个进程最多能有多少个线程