Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行的程序都可以使用线程池。

合理使用线程池能带来三个好处:

  1. 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
  2. 提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行;
  3. 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控。

 

线程池的实现原理

当提交一个任务时,线程池的处理流程如下

Java 线程池并发demo java线程池最佳实践_Java 线程池并发demo

很清晰的一幅图,简洁明了

  1. 我们提交一个任务到线程池时,线程池首先判断当前核心线程池是否已满,如果没有满的话,那么就直接创建一个新的线程来执行任务;如果核心线程池已满的话那么就去判断队列是否已满;
  2. 如果工作队列没有满的话,那么就将这个任务存储到工作队列中进行等待;如果没满的话,那么就去判断线程池是否已满;
  3. 如果线程池没有满的话,同样创建一个新的线程去执行任务;相反,如果线程池满的话那么就去按照饱和策略处理无法执行的任务。

ThreadPoolExcutor执行excute()方法的示意图

Java 线程池并发demo java线程池最佳实践_线程池_02

ThreadPoolExcute执行excute方法分下面四种情况:

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

ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将回事一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁。

源码分析

public void execute(Runnable command) {
            if (command == null)
                throw new NullPointerException();
            int c = ctl.get();
            /**
             *  如果运行的线程小于corePoolSize,则尝试用给定的命令作为第一个任务启动一个新线程。
             *  对addWorker的调用原子性地检查runState和workerCount,因此可以通过返回false来防止错误警报,因为错误警报会在不应该添加线程的时候添加线程。
             */
            if (workerCountOf(c) < corePoolSize) {
                if (addWorker(command, true))
                    return;
                c = ctl.get();
            }
           // 线程池处于RUNNING状态,并将任务放入workQueue队列
            if (isRunning(c) && workQueue.offer(command)) {
                int recheck = ctl.get();
               // 需要再次检查是否应该添加一个线程(因为自上次检查以来已有的线程已经死亡),或者池在进入这个方法后关闭。
                if (! isRunning(recheck) && remove(command))
                    reject(command);
                // 重新检查状态,如果必要的话,如果停止,则回滚队列;如果没有,则启动一个新线程。
                else if (workerCountOf(recheck) == 0)
                    addWorker(null, false);
            }
           // 如果队列满了,则尝试添加新线程。如果它失败了,则执行线程池的饱和策略。
            else if (!addWorker(command, false))
                reject(command);
        }

工作线程:线程池创建线程时,会将线程粉装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行。我们可以从Worker类的run()方法里看到。

public void run(){
    try{
        Runnable task = firstTask;
        firstTask = null;
        while(task != null || (task = getTask()) != null){
            runTask(task);
            task = null;
        }
    } finally {
        workerDone(this);
    }
}

ThreadPoolExecutor中线程执行任务的示意图如图

Java 线程池并发demo java线程池最佳实践_Java_03

线程池中的线程执行任务分两种情况,如下:

  1. 在execute()方法中创建一个线程时,会让这个线程执行当前任务;
  2. 这个线程执行完图中1的任务后,会反复从BlockingQueue获取任务来执行。

 

 

 

————《Java并发编程的艺术》 方腾飞  魏鹏 程晓明  著