线程池

使用线程池的好处:

  1. 降低资源消耗。重复利用已创建线程,降低线程创建与销毁的资源消耗。(线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务。)
  2. 提高响应效率。任务到达时,不需等待创建线程就能立即执行。
  3. 提高线程可管理性。统一分配、调优和监控。
  4. 防止服务器过载。内存溢出、CPU耗尽。

线程池的基本组成部分:

  1. 线程池管理器(ThreadPool):用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务;
  2. 工作线程(PoolWorker):我们把用来执行用户任务的线程称为工作线程,工作线程就是不断从队列中获取任务对象并执行对象上的业务方法。 
  3. 任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
  4. 任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

Java中线程池有关接口和类:

创建线程池:

public ThreadPoolExecutor(int corePoolSize,  //核心线程数,即使在无用时也不会被回收
                              int maximumPoolSize, //可容纳的最大线程数
                              long keepAliveTime,  //非核心线程可保留的最长空闲时间
                              TimeUnit unit,  //keepAliveTime的单位,如分钟(MINUTES)
                              BlockingQueue<Runnable> workQueue,  //任务等待队列
                              ThreadFactory threadFactory,  //创建线程的线程工厂
                              RejectedExecutionHandler handler  //任务满时的拒绝策略 ) 

     在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。     

Executors.newFixedThreadPool(int);    //创建固定容量大小的缓冲池
Executors.newSingleThreadExecutor();   //创建容量为1的缓冲池
Executors.newCachedThreadPool();        //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE
  1. newFixedThreadPool:创建的线程池corePoolSize和maximumPoolSize值是相等的(只有核心线程),它使用的LinkedBlockingQueue;
  2. newSingleThreadExecutor:将corePoolSize和maximumPoolSize都设置为1(只有一条核心线程来执行任务),也使用的LinkedBlockingQueue。适用于有顺序的任务的应用场景。
  3. newCachedThreadPool:将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。适用于耗时少,任务量大的情况。
  4.  newScheduledThreadPool :创建一个定长线程池,支持定时及周期性任务执行。

      实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。

向线程池提交新任务流程图:

  1. 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
  2. 如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
  3. 如果无法将任务加入BlockingQueue(队列已满),则在非corePool中创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
  4. 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

任务缓存队列:

       workQueue,它用来存放等待执行的任务,它的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

  1. ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
  2. LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
  3. synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。

handler的拒绝策略有四种:

  • ThreadPoolExecutor.AbortPolicy:丢弃新任务,直接抛出异常,提示线程池已满(默认)              
  • ThreadPoolExecutor.DisCardPolicy:丢弃新任务,也不抛出异常              
  • ThreadPoolExecutor.DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行              
  • ThreadPoolExecutor.CallerRunsPolicy:直接在当前线程中调用execute来执行当前任务

      此外,用户自定义拒绝策略:实现RejectedExecutionHandler,并自己定义策略模式 

向线程池提交新任务:

      execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。

threadsPool.execute(new Runnable() {
        @Override
        public void run() {
        }
    });

关闭线程池:

  ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

动态调整容量:

  ThreadPoolExecutor提供了动态调整线程池容量大小的方法:setCorePoolSize()和setMaximumPoolSize(),

  • setCorePoolSize:设置核心池大小
  • setMaximumPoolSize:设置线程池最大能创建的线程数目大小

合理配置线程池大小:

  一般需要根据任务的类型来配置线程池大小:

  如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1

  如果是IO密集型任务,参考值可以设置为2*NCPU

线程池的状态

volatile int runState;
static final int RUNNING = 0;
static final int SHUTDOWN = 1;
static final int STOP = 2;
static final int TERMINATED = 3;

当创建线程池后,初始时,线程池处于RUNNING状态;

  如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

  如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

      当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

 
原文地址:https://www.cnblogs.com/kikis/p/9770793.html