java线程池1-线程池,ThreadPoolExecutor类详解

来源:互联网 发布:留学中介 知乎 编辑:程序博客网 时间:2024/06/07 01:29

1.线程池的概念

多线程技术主要用于解决处理器单元内多个线程执行的问题,她可以显著减少处理器单元的闲置时间,增减处理器单元的吞吐能力。
多数生产环境面临的技术背景一般是:处理一次请求的时间是短暂的,但是请求数量巨大,这种情况如果为每一个请求都单独创建一个县城,那么资源基本都被创建线程,切换线程,销毁线程所占据,用于业务请求的资源反而少
理想的处理方式是:将处理请求的线程数量控制在一个范围,既保证后续的请求不会等待太长时间,又保证物理机将足够的资源用于请求处理本身。
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。

一个线程池包括以下四个基本组成部分:
1、线程池管理器(ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
2、工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
4、任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

2.为什么要用线程池?

1.创建、销毁线程伴随着系统的开销,过于频繁的创建销毁,很大程度上影响处理器的效率。
例如:
记创建线程消耗时间T1,执行任务消耗时间T2,销毁线程消耗时间T3
如果T1+T3>T2,那么是不是说开启一个线程来执行这个任务太不划算了!
正好,线程池缓存线程,可用已有的闲置线程来执行新任务,避免了T1+T3带来的系统开销
2.线程并发数量过多,抢占资源导致阻塞,线程池能有效控制线程的最大并发数。
3.对线程进行一些简单的管理,如:延时执行,定时循环执行的策略

3.线程池的ThreadPoolExecutor类

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类。
ThreadPoolExecutor类的四个构造方法如下:

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继承了AbstractExecutorService类,并提供了四个构造器.

参数的含义如下:

1.corePoolSize:线程池主要用于执行任务的是“核心线程”,“核心线程”的数量是您创建线程时所设置的corePoolSize参数决定的。如果不进行特别的设定,线程池中始终会保持corePoolSize数量的线程数(不包括创建阶段,创建阶段刚建线程池的时候线程数是0,当有任务到来的时候才开始创建线程,没达到corePoolSize,每来一个任务就会新创建一个线程,不管之前的是不是空闲。也可以在任务到来之前prestartAllCoreThreads()或者prestartCoreThread()方法预创建corePoolSize数量的线程)。
线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程,核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)
2.maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
线程总数 = 核心线程数 + 非核心线程数。
3.keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
4.unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS;               //天TimeUnit.HOURS;             //小时TimeUnit.MINUTES;           //分钟TimeUnit.SECONDS;           //秒TimeUnit.MILLISECONDS;      //毫秒TimeUnit.MICROSECONDS;      //微妙TimeUnit.NANOSECONDS;       //纳秒

5.workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:

  • ArrayBlockingQueue;
  • LinkedBlockingQueue;
  • SynchronousQueue;
  • DelayQueue;
     常使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
     SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
     LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize

6.threadFactory:线程工厂,主要用来创建线程;
handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
7.RejectedExecutionHandler handler:抛出异常专用的

线程池的逻辑结构:

这里写图片描述
注意:存在于线程池中容器的一定是Thread对象,而不是您要求运行的任务;您要求运行的任务将被线程池分配给某一个空闲的Thread运行。

几个重要因素:

等待队列:顾名思义,就是您调用线程池对象的submit()方法或者execute()方法,要求线程池运行的任务(这些任务必须实现Runnable接口或者Callable接口)。但是出于某些原因线程池并没有马上运行这些任务,而是送入一个队列等待执行(这些原因后文马上讲解)。
核心线程:线程池主要用于执行任务的是“核心线程”,“核心线程”的数量是您创建线程时所设置的corePoolSize参数决定的。如果不进行特别的设定,线程池中始终会保持corePoolSize数量的线程数(不包括创建阶段)。
非核心线程:一旦任务数量过多(由等待队列的特性决定),线程池将创建“非核心线程”临时帮助运行任务。您设置的大于corePoolSize参数小于maximumPoolSize参数的部分,就是线程池可以临时创建的“非核心线程”的最大数量。这种情况下如果某个线程没有运行任何任务,在等待keepAliveTime时间后,这个线程将会被销毁,直到线程池的线程数量重新达到corePoolSize。
也就是说,并不是所谓的“非核心线程”才会被回收;而是谁的空闲时间达到keepAliveTime这个阀值,就会被回收。直到线程池中线程数量等于corePoolSize为止。
maximumPoolSize参数也是当前线程池允许创建的最大线程数量。那么如果您设置的corePoolSize参数和您设置的maximumPoolSize参数一致时,线程池在任何情况下都不会回收空闲线程。keepAliveTime也就失去了意义。

说完了线程池的逻辑结构,下面我们讨论一下线程池是怎样处理某一个运行任务的。下图描述了一个完整的任务处理过程:
这里写图片描述
1、首先您可以通过线程池提供的submit()方法或者execute()方法,要求线程池执行某个任务。线程池收到这个要求执行的任务后,会有几种处理情况:

1.1、如果当前线程池中运行的线程数量还没有达到corePoolSize大小时,线程池会创建一个新的线程运行您的任务,无论之前已经创建的线程是否处于空闲状态。

1.2、如果当前线程池中运行的线程数量已经达到设置的corePoolSize大小,线程池会把您的这个任务加入到等待队列中。直到某一个的线程空闲了,线程池会根据您设置的等待队列规则,从队列中取出一个新的任务执行。

1.3、如果根据队列规则,这个任务无法加入等待队列。这时线程池就会创建一个“非核心线程”直接运行这个任务。注意,如果这种情况下任务执行成功,那么当前线程池中的线程数量一定大于corePoolSize。

1.4、如果这个任务,无法被“核心线程”直接执行,又无法加入等待队列,又无法创建“非核心线程”直接执行,且您没有为线程池设置RejectedExecutionHandler。这时线程池会抛出RejectedExecutionException异常,即线程池拒绝接受这个任务。(实际上抛出RejectedExecutionException异常的操作,是ThreadPoolExecutor线程池中一个默认的RejectedExecutionHandler实现:AbortPolicy,这在后文会提到)

2、一旦线程池中某个线程完成了任务的执行,它就会试图到任务等待队列中拿去下一个等待任务(所有的等待任务都实现了BlockingQueue接口,按照接口字面上的理解,这是一个可阻塞的队列接口),它会调用等待队列的poll()方法,并停留在哪里。

3、当线程池中的线程超过您设置的corePoolSize参数,说明当前线程池中有所谓的“非核心线程”。那么当某个线程处理完任务后,如果等待keepAliveTime时间后仍然没有新的任务分配给它,那么这个线程将会被回收。线程池回收线程时,对所谓的“核心线程”和“非核心线程”是一视同仁的,直到线程池中线程的数量等于您设置的corePoolSize参数时,回收过程才会停止。

ThreadPoolExecutor类常用的方法

首先了解继承关系的几个类:
ThreadPoolExecutor、AbstractExecutorService、ExecutorService、Executor
方法:
1.execute()
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
2.submit()
submit()方法是在ExecutorService中声明的方法,在AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
3.shutdown():用来关闭线程池的。
shutdownNow():用来关闭线程池的。
还有很多其他的方法:
  比如:getQueue() 、getPoolSize() 、getActiveCount()、getCompletedTaskCount()等获取与线程池相关属性的方法
  

ThreadPoolExecutor的策略

总结一下,当一个任务被添加进线程池时:
线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
线程数量达到了corePools,则将任务移入队列等待
队列已满,新建线程(非核心线程)执行任务
队列已满,总线程数又达到了maximumPoolSize,就会由上面那位星期天(RejectedExecutionHandler)抛出异常
  

package test.thread.pool;import java.util.concurrent.SynchronousQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;import org.apache.log4j.BasicConfigurator;public class PoolThreadSimple {    static {        BasicConfigurator.configure();    }    public static void main(String[] args) throws Throwable {        /*         * corePoolSize:核心大小,线程池初始化的时候,就会有这么大         * maximumPoolSize:线程池最大线程数         * keepAliveTime:如果当前线程池中线程数大于corePoolSize。         * 多余的线程,在等待keepAliveTime时间后如果还没有新的线程任务指派给它,它就会被回收         *          * unit:等待时间keepAliveTime的单位         *          * workQueue:等待队列。这个对象的设置是本文将重点介绍的内容         * */        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.MINUTES, new SynchronousQueue<Runnable>());        for(int index = 0 ; index < 10 ; index ++) {            poolExecutor.submit(new PoolThreadSimple.TestRunnable(index));        }        // 没有特殊含义,只是为了保证main线程不会退出        synchronized (poolExecutor) {            poolExecutor.wait();        }    }    /**     * 这个就是测试用的线程     * @author yinwenjie     */    private static class TestRunnable implements Runnable {        /**         * 日志         */        private static Log LOGGER = LogFactory.getLog(TestRunnable.class);        /**         * 记录任务的唯一编号,这样在日志中好做识别         */        private Integer index;        public TestRunnable(int index) {            this.index = index;        }        /**         * @return the index         */        public Integer getIndex() {            return index;        }        @Override        public void run() {            /*             * 线程中,就只做一件事情:             * 等待60秒钟的事件,以便模拟业务操作过程             * */            Thread currentThread  = Thread.currentThread();            TestRunnable.LOGGER.info("线程:" + currentThread.getId() + " 中的任务(" + this.getIndex() + ")开始执行===");            synchronized (currentThread) {                try {                    currentThread.wait(60000);                } catch (InterruptedException e) {                    TestRunnable.LOGGER.error(e.getMessage(), e);                }            }            TestRunnable.LOGGER.info("线程:" + currentThread.getId() + " 中的任务(" + this.getIndex() + ")执行完成");        }    }}

参考:https://www.cnblogs.com/exe19/p/5359885.html
http://www.jianshu.com/p/210eab345423
http://blog.csdn.net/yinwenjie/article/details/50522458
http://blog.csdn.net/hsuxu/article/details/8985931
http://www.jianshu.com/p/210eab345423

原创粉丝点击