线程池

来源:互联网 发布:淘宝店铺背景素材 编辑:程序博客网 时间:2024/06/16 20:09


回顾:对于线程,有两种实现方式:
    第一种是继承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> 


以上








0 0