您的位置:首页 > 其它

线程池

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中的线程池,必须先了解这个类!

解释一下下面几个重要的属性:

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>


以上
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  线程池 线程