Java线程池原理剖析

1,192 阅读5分钟

Java线程池作用和设计思想

作用

  • 提高线程的可管理性:Java线程池主要的作用就是具有良好的管理线程资源能力,防止无休止的去申请系统的线程资源,因为在我们的hotspot虚拟机中Java的线程模型是1:1的,每一个创建的Java线程都对应的一个OS中的轻量级线程,一般来说,创建几万个线程就对系统内存有非常大的压力
  • 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行
业务背景

线程池在我们的项目中大量的用到,比如在分割大请求,下载资源,或者是异步处理中都会用到这个技术

设计思想

  • 线程池中主要利用到的思想就是池化思想,利用这种池化技术,可以达到资源复用的效果。
  • 在线程池中,采用了类似生产消费者的模式来对任务进行处理。 大致的运行流程图如下:

Java线程池的参数和生命周期

参数

无论是哪一种方式构造线程池都是依赖ThreadPoolExecutor的构造方法来进行创建的,构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

从上面的构造方法可以看出,一个线程池的构造需要上面七种参数,分别是:

  1. corePoolSize: 核心线程池大小;
  2. maximumPoolSize: 最大线程池大小;
  3. keepAliveTime: 空闲线程的存活时间;
  4. unit: keepAliveTime的时间单位;
  5. workQueue: 用来装被阻塞任务的阻塞队列;
  6. threadFactory: 线程工厂, 用来生成线程池中工作线程的地方;
  7. handler: 拒绝策略, 当线程池停止或者最大线程和最大队列都满了的 时候,会调用拒绝策略里面的拒绝方法来处理任务。

上面有几个参数需要注意,第一个是keepAliveTime,单独这个参数是只能决定超出核心线程池大小的线程的存活时间,而如果要给我们的核心线程池的线程大小设置存活时间的话,还需要设置一个参数:allowCoreThreadTimeOut,源码如下(getTask()的部分源码):

// 根据allowCoreThreadTimeOut和当前线程数是否超出核心线程池大小决定是否会超时等待
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
......
try {
    // 如果允许超时等待,则会在阻塞队列为空的情况下,超时等待一段时间,如果等待一段时间之后依旧阻塞队列为空,则会返回为空
    // 如果不允许超市等待,那么线程就会阻塞在阻塞队列里面的Condition队列里面,直到队列中有任务被被唤醒
    Runnable r = timed ?
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
        workQueue.take();
    if (r != null)
        return r;
    timedOut = true;
} catch (InterruptedException retry) {
    timedOut = false;
}

从上面可以看出来,工作线程之间没有任何差别,核心和非核心只针对数目,而不是针对具体线程,就算设置了allowCoreThreadTimeOut为false,在高并发情况下,如果阻塞队列为空,也会销毁没有拿到任务的线程,然后重新添加工作线程,直到工作线程数等于核心线程数,销毁线程源码如下:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();
    final ReentrantLock mainLock = this.mainLock;
    // 使用线程池的锁来锁住,保证删除线程和统计线程执行任务数的逻辑线程安全
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    int c = ctl.get();
    // 判断线程池的状态是否正常运行
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            // 获取最小线程数目
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 如果当前工作线程数大于获取到的最小线程数,就不会再新增加线程
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 新增工作线程
        addWorker(null, false);
    }
}

第二个是workQueue:在jdk内部的阻塞队列有很多种,但是不管是哪一种阻塞队列,都需要对阻塞队列的大小设置一个合理的大小,设置太大的话可能会导致最大线程数不生效,设置太小的话,可能在高并发情况下会导致最大线程没有来得及开启的情况下,任务打进队列,发现长度不够,直接调用了拒绝策略。
示例代码:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
        5,
        5,
        0,
        TimeUnit.SECONDS,
        new ArrayBlockingQueue<Runnable>(3)
);
for (int i = 0; i < 5; ++i) {
    executor.submit(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(900);
            } catch (InterruptedException ignored) {
            }
        }
    });
}
Thread.sleep(1000);
System.out.println("After sleep");
for (int i = 0; i < 5; ++i) {
    try {
        executor.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(900);
                    System.out.println("执行完成");
                } catch (InterruptedException ignored) {
                }
            }
        });
    } catch (Exception e) {
        System.out.println(e);
    }
}

除了这七种熟知的参数外,还有几个很重要的参数:

  1. ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

ctl这个参数包含两个部分,第一个是高三位,这个里面包含的线程池的运行状态,第二个是低29位,包含的是线程池运行的工作线程数目 2. mainLock

private final ReentrantLock mainLock = new ReentrantLock();

这个是线程池内部的一个可重入锁,用来控制线程池的各种同步

生命周期

ThreadPoolExecutor的生命周期有五种状态,分别是: 状态流转入如下图:

ThreadPoolExecutor源码分析

我们从两个个方向来做ThreadPoolExecutor的源码分析

  • 执行任务
  • 工作线程

执行任务

在ThreadPoolExecutor中,创建完线程池之后,是通过execute方法来执行任务

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 查询当前运行的线程数是否小于核心线程数
    // 如果小于的话会直接新增一个线程并且返回
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 如果运行的线程数目已经大于等于核心线程数的话
    // 就会判断当前线程池是否正常运行,并且尝试往阻塞队列中放入该执行任务
    if (isRunning(c) && workQueue.offer(command)) {
        // 如果大于核心线程数目并且任务插入阻塞队列成功的话
        // 会去重新取线程池的状态值
        int recheck = ctl.get();
        // 如果发现线程池不是处于运行状态,就会去阻塞队列中移除改执行任务,并且调用拒绝策略
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 否则如果发现当前运行工作线程数目为0的话就会尝试新增工作线程
        // 到这里的时候工作线程是0只有两种情况,第一种情况是我们设置的corePoolSize是0,第二种情况是运行到这里时,刚好线程已经过了存活时间被回收了
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 如果上面队列放不进去就会新增超出核心线程数的线程
    else if (!addWorker(command, false))
        reject(command);
}

上诉的工作流程可以简化为下图:

这个里面还涉及到添加工作线程的流程,addWorker方法

private boolean addWorker(Runnable firstTask, boolean core) {
    // 通过CAS + 循环来对工作线程的计数增加
    retry:
    for (;;) {
        // 获取当前线程池的状态
        int c = ctl.get();
        int rs = runStateOf(c);
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        for (;;) {
            int wc = workerCountOf(c);
            // 查询当前的线程数是否有增加的余地
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // CAS增加线程数
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 新增一个工作线程
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 启动工作线程
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

工作线程

上述过程一直提到了一个东西就是工作线程,实际上工作线程并不仅仅单独指一个线程,工作线程的具体组成有几个东西,第一个是我们真正被运行的线程,第二个是刚刚创建线程时候会放进去的第一个任务,第三个是内部基于AbstractQueuedSynchronizer实现的一套独占锁,这套独占锁是不可重入的。当一个工作线程运行时,会调用工作线程里面的run方法,而run方法里面实际上调用的是ThreadPoolExecute里面的runWorker方法,runWorker方法源码:

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 这个是防止在开始执行任务之前,有中断请求,保证可以响应中断
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 首先检查当前的工作线程有没有携带任务,如果没有携带任务就会通过getTask方法去阻塞队列中间拿任务
        // 这个地方的循环就是线程池中资源复用的体现
        while (task != null || (task = getTask()) != null) {
            // 这个地方会锁住当前工作线程,保证线程安全
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                // 执行任务前的前置操作
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 具体执行任务
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    // 执行任务之后的操作
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        // 走到这里表明线程是正常结束,不是因为执行任务中有异常
        completedAbruptly = false;
    } finally {
        // 销毁工作线程
        processWorkerExit(w, completedAbruptly);
    }
}

简化流程如下:

参考:
Java线程池实现原理及其在美团业务中的实践