JAVA 线程池

来源:互联网 发布:java 预测算法 编辑:程序博客网 时间:2024/06/03 15:11

原作者:海子   

出处:http://www.cnblogs.com/dolphin0520/    

本文归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

1.为什么要使用线程池

当我们使用线程的时候, 就去创建一个线程, 虽然使用起来很简单,  但是如果并发的线程数量很多, 并且每个线程都是执行完一段时间很短的任务就结束了, 这样频繁的去创建线程就会大大的浪费系统的性能, 因为频繁的去创建线程和销毁线程是需要时间的。

2. 线程池的作用

线程池的作用就是执行完一个线程之后并不销毁, 而是继续去执行其他的任务, 从而达到提高系统性能的目的。

一.Java中的ThreadPoolExecutor类

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池

public class ThreadPoolExecutor extends AbstractExecutorService {.....public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);...}

  ThreadPoolExecutor提供了四种构造方法, 以下是几个参数的说明

  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
  • maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
  • workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
  • threadFactory:线程工厂,主要用来创建线程
  • handler:表示当拒绝处理任务时的策略
ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor 的关系
 

Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;

  然后ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;

  抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;

  然后ThreadPoolExecutor继承了类AbstractExecutorService。

  所以在ThreadPoolExecutor类中有几个非常重要的方法:

execute()submit()shutdown()shutdownNow()
下面对以上几个方法进行详细解释
execute(Runable runable) 由顶层接口Executor提供, ThreadPoolExecutor对其进行了具体的实现, 方法的参数为传入Runable对象, 通过这个方法可以向线程池提交一个任务, 交给线程池去执行。
submit(Runable task) 由 ExecuteService提供, 由AbstractExecuteService进行了重写,实际底层同样是调用了execute()方法,  但是执行submit会返回执行结果
shutdown() 当线程池调用此方法时, 线程池就会马上处于shutdown状态,不能再往线程池中添加任何任务,否则会抛出RejectExcutionException异常当线程池中的任务执行完了则会退出
shutdownNow()当线程池调用了此方法时, 会马上处于Stop状态, 并试图停止还在队列中的所有任务, 底层是调用Thred.interrupt方法去实现
shutdown 和 shutdownNow 适用场景
 shutdown 所有的任务都是一个单独的task, 相互之间没有关系, 其中一个线程发生异常并不会影响其他线程, 适合用shutdown
 shutdownNow 线程池中所有的任务都是做同一件事, 其中一个线程发生异常会直接影响整个结果,失去继续执行下去的意义

二、线程池实现原理

1.线程池的状态
  
   ThreadpoolExecutor类中定义一个 volatile变量 runsState变量用来标记当前线程池的状态 
volatile int runState;static final int RUNNING    = 0; // 正在运行static final int SHUTDOWN   = 1; // 不在接受新的任务, 继续执行剩下的任务, 执行完成之后退出static final int STOP       = 2; // 同样不接受新的任务,并试图停止正在运行的任务static final int TERMINATED = 3; // 已经终止
2.ThreadpoolExecutor 主要变量介绍
private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小                                              //、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集 
private volatile long  keepAliveTime;    //线程存活时间   
private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数 
private volatile int   poolSize;       //线程池中当前的线程数 private volatile RejectedExecutionHandler handler; //任务拒绝策略 private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程 private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数 private long completedTaskCount;   //用来记录已经执行完毕的任务个数

3. corePoolSize , maximumPoolSize , largestPoolSize 之间的解释
corePoolSize     为核心池的大小,
maximumPoolSize  线程池最大可容纳线程的大小
largestPoolSize  用来记录线程中曾经有过最大的线程池数目
   用一个简单的例子简要说明三个变量之间关系
假设一个工厂只有10个工人, 每个工人同时只能做一件事, 当工厂所下的订单增长速度远远大于工人的产出速度, 这时候老板可能就会多招收5个临时工,
用来干活, 这个时候工厂就会有15个人,当剩下的订单10个人能够完全应对的时候, 就会辞掉5个临时工, 10个人则为corePoolSize, 
(10+5)则为maxmunPoolSize, 同时lagestPoolSize会记录工厂最的高人数为15个人
maxmunSize也可看做是一种临时补救措施
4.任务从提交到最后执行完毕需要经历的过程
    在TheadPoolExecutor最核心的方法就是execute, 虽然submit也可以提交任务, 但是submit最终调用的还是execute方法
5.ThreadPoolExecutor execute方法详细解释
  public void execute(Runnable command) {
// 1.如果传入的线程为空, 直接报空指针        if (command == null)            throw new NullPointerException();
// 2.如果当前线程数量大于核心线程池容量的大小,并且线程池处于运行状态,则将线程放入执行队列中        if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { 
    //addIfUnderCorePoolSize方法只是对 poolSize >= corePoolSize 表达式二次多线程判断

            if (runState == RUNNING && workQueue.offer(command)) {
                if (runState != RUNNING || poolSize == 0)
                    ensureQueuedTaskHandled(command); // 此方法用于保证当其他线程调用shutdown方法时,仍然保证任务可以继续执行
            }
    // 如果放入队列失败, 一般情况是队列已经满了, 会调用addIfUnderMaximumPoolSize方法尝试重新创建新的线程去执行,
    // 如果当前线程没有超过设定最大的线程数,则执行成功, 但是当当前线程已经超过设定最大的线程数,则会驳回
            else if (!addIfUnderMaximumPoolSize(command))                reject(command); // is shutdown or saturated        }    }
6. 任务提交给线程池的处理策略
      a.当当前线程数量小于核心线程池容量的时候, 提交一个任务,则会创建一个线程进行处理
      b.当当前线程数量大于核心线程池容量的时候, 提交一个任务,则会添加到队列当中,等待其他线程处理完毕之后取出后执行,
若队列添加失败,一般是队列已满,则会重新创建线程去处理
    c.如果当前线程超过了最大线程池数量,则会采取驳回策略
      d.如果当前线程数量超过了核心线程池数量的情况下,某线程空闲时间超过了keepAliveTime时间,线程会终止
7.线程池线程初始化
一般情况创建线程池之后不会有线程,需要提交任务之后才会创建线程,如果需要创建线程池之后立即创建线程,可以调用以下两个方法办到
prestartCoreThread() 初始化一个核心线程
prestartAllCoreThread()初始化所有核心线程
8.线程池容量动态调整
ThreadPoolExecutor 提供了setCoreThreadSize()和 setMaxiMumPoolThread()

0 0