背景
   当系统并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要消耗大量的系统资源。
 
  所以需要一个办法使得线程可以复用,即当线程执行完一个任务,并不被销毁,而是可以继续执行其他的任务。在Java中就可以通过线程池来实现这样的效果。本文讲述了Java中的线程池类以及如何使用线程池。
 
一、java中的线程池ThreadPoolExecutor
   ThreadPoolExecutor是线程池中基础类也是最为核心的类。想要了解和合理使用线程池绕不开ThreadPoolExecutor类。下面介绍一下此类。
该类的构造函数如下
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
  //...
}
ASP站长网线程池有这么几个重要的参数
 
corePoolSize: 线程池里的核心线程数量
maximumPoolSize: 线程池里允许有的最大线程数量
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。 默认情况下,如果当前线程数量 > corePoolSize,多出来的线程会在keepAliveTime之后就被释放掉,直到线程池中的线程数不大于corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0
unit: keepAliveTime的时间单位,有7种单位
TimeUnit.DAYS;                           //天
TimeUnit.HOURS;                        //小时
TimeUnit.MINUTES;                    //分钟
TimeUnit.SECONDS;                  //秒
TimeUnit.MILLISECONDS;         //毫秒
TimeUnit.MICROSECONDS;       //微妙
TimeUnit.NANOSECONDS;        //月     
workQueue: 队列workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:
有界任务队列ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小; 
无界任务队列LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
直接提交队列synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。如果进程数量已经达到最大值,则执行拒绝策略。因此使用该队列需要设置很大的maximumPoolSize,否则很容易执行拒绝策略。
threadFactory: 每当需要创建新的线程放入线程池的时候,就是通过这个线程工厂来创建的
handler: 就是说当线程,队列都满了,之后采取的策略,比如抛出异常等策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,不做任何处理
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
二、 线程池中重要的方法 
线程池有两个重要的操作,提交任务和关闭线程池。在讲述这两个操作之前先了解一下线程池的状态。注意!!!,线程池的状态而不是线程状态。
 
在ThreadPoolExecutor中定义了一个volatile变量,另外定义了几个static final变量表示线程池的各个状态:
 
//当前线程池的状态,voliate 保证了线程之间的可见
volatile int runState;
 
//创建线程池后,初始时,线程池处于此状态
static final int RUNNING    = 0;
 
/*调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕*/
static final int SHUTDOWN   = 1;
 
/*调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务*/
static final int STOP       = 2;
 
/*线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态*/
static final int TERMINATED = 3;
2.1 线程池提交任务
ThreadPoolExecutor的提交操作可以使用submit和execute这两种方法
 
2.1.1 excute
 最核心的任务提交方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法 并且ExecutorService中的invokeAll(),invokeAny()都是调用的execute方法。execute提交的任务无返回值,因此无法判断任务是否执行成功。但是如果出现线程错误可以显示部分异常堆栈信息
 
public void execute(Runnable command) {
 
if (command == null)
 
throw new NullPointerException();
 // 1.当前线程数量小于corePoolSize,则创建并启动线程。
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
        // 成功,则返回
                return;
            c = ctl.get();
        }
    // 2.步骤1创建线程失败,则尝试把任务加入阻塞队列,
        if (isRunning(c) && workQueue.offer(command)) {
       // 入队列成功,检查线程池状态,如果状态部署RUNNING而且remove成功,则拒绝任务
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
       // 如果当前worker数量为0,通过addWorker(null, false)创建一个线程,其任务为null
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
    // 3. 步骤1和2失败,则尝试将线程池的数量由corePoolSize扩充至maxPoolSize,如果失败,则拒绝任务
        else if (!addWorker(command, false))
            reject(command);
    }
 }
详细流程解读
 
  workerCountOf方法根据ctl的低29位,得到线程池的当前线程数,如果线程数小于corePoolSize,则执行addWorker方法创建新的线程执行任务;否则执行步骤(2); 
 如果线程池处于RUNNING状态,且把提交的任务成功放入阻塞队列中,则执行步骤(3),否则执行步骤(4)
 再次检查线程池的状态,如果线程池没有RUNNING,且成功从阻塞队列中删除任务,则执行reject方法处理任务
执行addWorker方法尝试将线程池的数量由corePoolSize扩充至maxPoolSize,如果addWoker执行失败,则执行reject方法处理任务;
2.1.2 sumbit
      如果使用submit 方法来提交任务,它会返回一个future,那么我们可以通过这个future来判断任务是否执行成功,通过future的get方法来获取返回值,get方法会阻塞住直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。可以通过以下方式改造submit获得部分异常堆栈信息
 
try {
 Future re = pools.submit(Task);
 re.get();
 
} catch (InterruptedException e) {
 
// 处理中断异常
 
} catch (ExecutionException e) {
 
// 处理无法执行任务异常
 
} finally {
 
// 关闭线程池
 
executor.shutdown();
 
}
2.2 线程池的关闭
使用线程池时,我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,但是它们的实现原理不同。
 
shutdown的原理是只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
shutdownNow的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。
 
三、线程池种类
常见的五种线程池
 
newFixedThreadPool:固定数量线程池,这个比较常用.可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值
newSingleThreadExecutor: 单线程线程池,一般很少使用.
newCachedThreadPool:缓存线程池,缓存的线程默认存活60秒,线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。
newScheduledThreadPool:定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。
newWorkStealingPool:工作窃取线程池,该线程为jdk1.8版新增。

dawei

【声明】:九江站长网内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。