读懂Java线程池

Java容器

在这里插入图片描述

在Java中,容器共有2大类,一种是Collection,另一种是map。其中Collection下又有2中集合类型,一种是普通存储数据类的list以及set,还有一种是为高并发服务的queue。为什么要提一下容器呢,因为我们下面提到的线程池将会用到这里的queue。

线程池

在Java中有两种线程池

  • ThreadPoolExecutor
    自定义线程池,可以定义线程数量,等待队列,拒绝策略等
  • ForkJoinPool
    Fork/Join框架是Java 7提供的一个用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

主要的区别

  1. 使用场景不同,ThreadPoolExecutor可以自定义任务,ForkJoinPool适合具有父子关系的任务,适合分治算法
  2. 使用方法不同,ThreadPoolExecutor需要自定义线程池大小,等待队列具体类型(ArrayQueue需要指定queue长度,LinkQueue长度则为int的最大值),ForkJoinPool线程数量默认是CPU的核心数,等待队列近乎无限,有造成OOM的风险

后面线程池以ThreadPoolExecutor为基数进行讨论。

Executor、Future、FutureTask

Executor

Executor接口,是线程池的根接口:
在这里插入图片描述
下面我们来看下ThreadPoolExecutor的类关系图:
在这里插入图片描述
从第一幅截图中我们可以看到Executor接口中定义了一个execute方法,看方法名也很好理解,用来执行task,同时返回值类型为void。

下面我们简单看下其子接口ExecutorService中又定义了哪些内容:

void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit) throws InterruptedException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

很好理解,定义了一些shutdown方法,以及执行单个task,多个task的方法。

我们以submit为例:
我们一定可以看到方法的入参是Callable,或者Runnable,其方法返回值是个Future,这都啥玩样儿?
不急,我们看源码。

首先我们看下Runnable:

public interface Runnable {
    public abstract void run();
}

再看下Callable:

public interface Callable<V> {
    V call() throws Exception;
}

Future

至此,我们就可以弄清楚了,Runnable执行run方法,没有返回值,Callable执行的是call方法,这个是有返回值的,也就是说通过call方法,我们就可以拿到线程执行task的结果了。放到线程池中,对应的返回值类型就是个Future,那这个Future又是个啥呢?

public interface Future<V> {
	boolean cancel(boolean mayInterruptIfRunning);
	boolean isCancelled();
	boolean isDone();
	V get() throws InterruptedException, ExecutionException;
	V get(long timeout, TimeUnit unit)  throws InterruptedException, ExecutionException, TimeoutException;
}

根据接口定义,我们可以很清楚的知道了,Future封装了thread的运行状态,以及thread运行后的返回值,通过get方法,就可以拿到线程的执行结果。但是,请注意,这个get方法是阻塞的,如果线程内部有死循环等问题,线程将会一直堵塞下去,风险系数非常高。
建议使用其子类CompletableFuture,JDK已经封装了很多方便的异步逻辑,具体使用线程池这部分就不多做介绍了,后面有机会再写。

FutureTask

public class FutureTask<V> implements RunnableFuture<V> {
	private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;
    private Callable<V> callable;
    private Object outcome; // non-volatile, protected by state reads/writes
    private volatile Thread runner;
    private volatile WaitNode waiters;
    .
    .
    .
}

摘抄部分,可以看到FutureTask即实现了接口RunnableFuture,又持有Callable对象,可以认为FutureTask同时拥有Runnable和Callable特性。

ThreadPoolExecutor

终于讲到ThreadPoolExecutor了,我们先看到pool的构造器,以参数最全的那个为例:

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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize:核心线程数,这部分的线程就算空闲,也不会归还给os
maximumPoolSize:最大线程数,当任务量太多,核心线程处理不过来的时候,会动态扩容到的最大线程数量
keepAliveTime:线程最大空闲时间,空闲超过该时间,该线程归还给os
unit:时间单位
workQueue:线程阻塞queue,用来缓存暂时处理不过来的任务
threadFactory:线程工厂,用来定义以什么样的方式创建线程
handler:拒绝策略,当任务超过最大线程数之后,对后续任务怎么做处理。jdk自带4种策略,建议自定义。

执行过程

在这里插入图片描述
假设我们现在已经创建出了一个线程池,核心线程数2,最大线程数4,任务队列大小2。现在有7个task需要被执行,过程如下:

  • task1:此时线程池内没有线程,首先创建出一个线程thread1来执行task1
  • task2:核心线程数为2,现有线程数为1,没有超过设定值,创建线程thread2来执行task2
  • task3:核心线程数2,现有线程数2,已经达到设定值,且等待队列为空,将task3放入等待队列
  • task4:与task3类似,等待队列没有满,将task4放入等待队列
  • task5:核心线程数已满,等待队列已满,但是最大线程数为4,现有线程为2,则创建线程thread3执行task5
  • task6:与task5类似,还没有达到最大线程数,创建thread4执行task6
  • task7:核心线程已满,最大线程已满,等待队列已满,线程池无法消化该任务,执行拒绝策略。

实现原理

我们以submit为例,探讨下线程池工作原理:
首先是入口方法:

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
}

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
 }

首先先将要执行的task封装为RunnableFuture对象ftask(实际是new了一个FutureTask),这个对象本身是Runnable接口的实例,所以可以直接将ftask扔给execute方法,执行task,同时将ftask返回(即实际返回类型是个FutureTask)。

下面再看下execute方法:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;   // 32-3 = 29
private static final int CAPACITY   = (1 << COUNT_BITS) - 1; //最大线程数 2^29 -1 
private static final int RUNNING    = -1 << COUNT_BITS;		//线程池运行状态
private static final int SHUTDOWN   =  0 << COUNT_BITS;		// 线程池关闭状态,不接受新任务,会处理完已经接受的任务
private static final int STOP       =  1 << COUNT_BITS;		// 立即关闭线程池,中断正在处理的任务
private static final int TIDYING    =  2 << COUNT_BITS;		// 所有任务已完成
private static final int TERMINATED =  3 << COUNT_BITS;

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);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

从上面的源码中可以看出,线程池的状态以及数量,都跟COUNT_BITS这个变量有关系:

  • COUNT_BITS:使用了int 32位的后29位,
  • RUNNING: -1 << COUNT_BITS
    -1在Java底层是由32个1表示的,左移29位的话,即111 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位全部为1的话,表示RUNNING状态,即-536870912;
  • SHUTDOWN = 0 << COUNT_BITS
    0在Java底层是由32个0表示的,无论左移多少位,还是32个0,即000 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位全部为0的话,表示SHUTDOWN状态,即0;
  • STOP = 1 << COUNT_BITS
    1在Java底层是由前面的31个0和1个1组成的,左移29位的话,即001 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为001的话,表示STOP状态,即536870912;
  • 后面还有两个也类似,就不写了

从上面的解释我们便可以看出,ctl的前3位表示的是当前线程池的运行状态,后29位代表是当前线程池中活动线程的数量,每当新增或者减少,只需要改动后29位中的值就可以了。

当有了这层理解之后,再看下面的execute方法就很容易了:

  1. 获取当前的ctl
  2. 根据ctl的后29位,判断当前线程数是否小于核心线程数,如果是,执行addWorker,直接返回
  3. 如果核心线程都被占满了,判断当前线程池是否是running状态,如果是,则通过offer方法,将当前任务添加至任务队列
  4. 再次尝试执行当前task,从缓存queue中取出该task,如果当前核心线程还是被占着,则调用addWorker,传入false参数,表示创建非核心线程
  5. 如果在第三步的时候,queue已经满了,无法添加,则直接调用addWorker启动非核心线程执行,如果执行失败,则执行拒绝策略。

所以,任务运行的核心就在于addWorker方法,我们一起来看下其源码,代码比较多,主要有两块内容,我们先看其中的第一部分:

private boolean addWorker(Runnable firstTask, boolean core) {
        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;
                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
            }
        }
        
		// 后面是第二部分,等下再讲
       .
       .
       . 
    }
    
private boolean compareAndIncrementWorkerCount(int expect) {
        return ctl.compareAndSet(expect, expect + 1);
}

通过上面的解释,我们大概可以知道addWorker方法的功能,即在线程池中添加线程来执行,这一部分的内容写了两层自旋,在多线程环境下,通过一层层的状态判断,最终目的就是为了执行compareAndIncrementWorkerCount方法,也就是在后29位的数字基础上+1,表示当前活跃的线程数量+1。

也就是,只有在判断线程池中能够创建新线程,之后,才能去执行task,这个也很好理解。

下面看第二部分:

		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;
}

Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
}

主要的逻辑有下面几步:

  1. 将当前任务封装为一个Worker,怎么封装的呢,见Worker的构造器,很简单,在创建线程池的时候,有一个参数代表的是如何创建thread的ThreadFactory,创建出一个线程之后,将当前的任务绑定到该thread
  2. 执行:workers.add(w); 将新建的worker放入workers集合中去
  3. 添加成功之后,执行:t.start();执行当前worker绑定的线程

到此处,我们往线程池中添加任务,线程池执行任务,就完成了。

线程复用

等等,似乎少了点什么,是什么呢?
我们回顾下上面的过程,在上面的过程中,线程总是被ThreadFactory创建出来,然后绑定task,然后在执行task。
是不是跟我们锁理解的线程池有点不一样呢?我们使用线程池的目的就是为了实现线程的复用,而目前看下来,线程池所做的事情,是在不停的new线程,什么时候复用的呢??

是不是很困惑呢?

我们知道,Thread.start()只能调用一次,一旦这个调用结束,则该线程就到了stop状态,不能再次调用start。

所以要实现复用,只能是在run方法里面做文章了,线程池是怎么做的呢?

public void run() {
            runWorker(this);
 }

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            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);
        }
    }

由此我们可以看到,线程的run实际最终执行的是runWorker方法,看到了吗,while 循环!是不是很兴奋?
没错,当线程创建出来之后,如果当前线程上有绑定task,则执行当前task,如果没有绑定task,则根据getTask方法去获取task,然后执行task。

再追踪下getTask方法:

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

我们看到了什么?workQueue.poll !!任务队列啊,简直热泪盈眶。

至此,我们便弄清楚了线程池是如何创建线程以及线程复用的。

  1. 如果线程池为空,或者活跃的线程数量<核心线程数,或者缓存队列已满,但活跃线程数量没有超过最大线程数时,创建新线程,在创建线程的过程中,绑定当前的task,并执行
  2. 当前task执行完毕之后,该线程并没有退出,而是再次从任务缓存列队中取出第一条task继续执行,只要能获取到获取到任何一条task,当前Thread都不会退出,会一直执行下去
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章