JUC---ThreadPoolExecutor线程池源码解析(JDK13)

java.util.concurrent包系列文章
JUC—ThreadLocal源码解析(JDK13)
JUC—ThreadPoolExecutor线程池源码解析(JDK13)
JUC—各种锁(JDK13)
JUC—原子类Atomic*.java源码解析(JDK13)
JUC—CAS源码解析(JDK13)
JUC—ConcurrentHashMap源码解析(JDK13)
JUC—CopyOnWriteArrayList源码解析(JDK13)
JUC—并发队列源码解析(JDK13)
JUC—多线程下控制并发流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)


线程池的组成

  • 线程池管理器 -> thread-pool
  • 工作线程(线程池中在运行的线程)-> t0,t1,t2…t9
  • 任务队列-> blocking-queue
  • 任务接口(task)-> 队列中的task1,task2…

在这里插入图片描述

  • Executor 顶层接口,只有一个 execute方法
void execute(Runnable command);
  • ExecutorService 定义了一些管理线程池,判断线程池状态的方法
void shutdown();优雅的关闭线程池(开始拒绝任务,并等待正在执行的线程执行完)
List<Runnable> shutdownNow();立即停止线程池,中断正在执行的任务
boolean isShutdown();返回线程池是否停止
<T> Future<T> submit(Runnable task, T result);提交新的任务到线程池
。。。
  • ThreadPoolExecutor 真正的线程池类,Executors 的快捷创建线程池的方法内部就是new ThreadPoolExecutor()创建的线程池。

比如 newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

在这里插入图片描述

  • Executors 就是一个线程池工具类,提供了一些简单创建线程池的方法。一般不用,它的缺点后面会介绍。

在这里插入图片描述


ThreadPoolExecutor源码解读

void execute(Runnable command)
public void execute(Runnable command) {
	// null就直接抛异常
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 首先检查是否小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
    	// 如果当前工作的线程数小于核心线程数,则增加一个线程来运行任务,command就是提交进来的任务
    	// 第二个参数的意思:true表示在增加线程时判断是否小于核心线程数,false表示判断是否小于最大线程数
        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了,则直接创建新线程执行任务
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 走到这里说明,要么线程数大于等于核心线程,要么线程池停止了,要么队列也满了
    // 就直接创建新的线程执行任务(当然,这个数量要小于最大线程数,如果达到了最大线程数,则执行拒绝策略)
    // addWorker()方法返回是否增加成功
    else if (!addWorker(command, false))
        reject(command);
}

以上就是线程池最核心的流程,后面会给出流程图说明。
再来看看线程池是如何做到线程不停止,直接复用执行任务的。

boolean addWorker(Runnable firstTask, boolean core);
 private boolean addWorker(Runnable firstTask, boolean core) {
   retry:
    // 这一部分是for死循环,通过CAS操作修改当前线程数
    for (int c = ctl.get();;) {
        // Check if queue empty only if necessary.
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, STOP)
                || firstTask != null
                || workQueue.isEmpty()))
            return false;
        for (;;) {
            if (workerCountOf(c)
                >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateAtLeast(c, SHUTDOWN))
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    	// 最重要的就是这里,把提交的任务封装成了Worker对象
        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 c = ctl.get();
                if (isRunning(c) ||
                    (runStateLessThan(c, STOP) && firstTask == null)) {
                    if (t.getState() != Thread.State.NEW)
                        throw new IllegalThreadStateException();
                    // workers是一个HashSet<Worker> ,接下来看runWorker()方法怎么复用执行任务的
                    workers.add(w);
                    workerAdded = true;
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
void runWorker(Worker w)
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循环,通过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);
                try {
                	// 最关键的这里,就是执行了我们提交的每一个线程任务的run方法来执行任务的。只不过Worker 包装了一下
                    task.run();
                    afterExecute(task, null);
                } catch (Throwable ex) {
                    afterExecute(task, ex);
                    throw ex;
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

最后看看

Runnable getTask()
private Runnable getTask() {
   boolean timedOut = false; // Did the last poll() time out?
    for (;;) {
        int c = ctl.get();
        // Check if queue empty only if necessary.
        if (runStateAtLeast(c, SHUTDOWN)
            && (runStateAtLeast(c, 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;
        }
    }
}

源码就贴到这里,线程池的整个流程应该比较清晰了。下面再补充一下。


ThreadPoolExecutor参数

  • corePoolSize:核心线程数,线程池完成初始化以后,线程池中并没有任何线程,线程池会等待有任务到来时,再创建线程去执行任务。
  • maxPoolSize:最大线程数
  • keepAliveTime:空闲线程存活时间。如果线程池当前的线程数多于corePoolSize,多于的线程空闲时间超过keepAliveTime,它们就会终止
  • ThreadFactory:新的线程由ThreadFactory创建,默认使用Executors.defaultThreadFactory(),自己制定ThreadFactory,可以设置线程名,线程组,优先级,是否是守护线程等。
  • workQueue:工作队列
    • 直接交接:SynchronousQueue,不存储任务,任务进来直接创建线程处理,可以无限创建线程
    • 无界队列:LinkedBlockingQueue,如果处理的速度跟不上任务提交的速度,那么任务会越来越多。有OOM风险。
    • 有界队列:ArrayBlockingQueue,可以设置队列大小

添加线程的规则流程

新任务来的时候,即使其他工作线程处于空闲,也会创建一个新线程来运行新任务。 如果线程数>=corePoolSize但少于maxPoolSize。则将任务放入队列中。如果队列已,并且线程数小于maxPoolSize,则创建一个新线程来运行任务。如果队列已满并且线程数>=maxPoolSize,则拒绝该任务。

在这里插入图片描述

JDK默认提供的线程池(Executors工具类中)

  • newFixedThredPool:无界队列,容易造成占用大量内存导致OOM。
  • newSingleThreadExecutor:单线程,无界队列,请求堆积的时候也会导致OOM。
  • newCachedThreadPool:corePoolSize设置为0,maxPoolSize设置为Integer.MAX_VALUE使用的SynchronousQueue。来了任务直接就给线程执行了。请求堆积的时候也会导致OOM。用完线程就全部回收销毁。
  • newScheduledThreadPool:延迟队列DelayedWorkQueue,支持定时以及周期性执行任务。使用于要重复运行的任务。类似于定时任务。
  • workStealingPool:1.8加入。处理子任务。子任务放到每个线程独有的任务队列中,某个线程执行完了,可以窃取别的线程中子任务来执行。递归+子任务的场景使用
    在这里插入图片描述

线程池的状态

  • RUNING:接受新任务并处理排队任务
  • SHUTDOWN:不接受新任务,但处理排队任务
  • STOP:不接受新任务,也不处理排队任务,并中断正在执行的任务
  • TIDING:所有任务都已终止,workCount为零时,线程会转换到TIDING状态。并将运行terminate()钩子方法。
  • TERMINATED:terminate()运行完成

停止线程池

  • shutdown:优雅的关闭线程池。执行完队列中的存量任务,拒绝新任务加入。
  • isShutdown:线程是否进入停止状态(boolean)。
  • isTerminated:返回线程是否真的停止,指任务全都执行完毕。
  • awaitTermination:检测线程是否在指定的时间内停止了。会阻塞等待指定时间。
  • shutdownNow:立刻关闭线程池 。正在执行的线程会收到interruptedException异常。返回一个没有执行的任务List。

线程池拒绝任务

  • 当Executor关闭时,提交新任务会被拒绝。
  • 当Executor线程满和队列也满了会拒绝。
拒绝策略
  • AbortPolicy:抛出异常
  • DiscardPolicy:丢弃新提交的任务
  • DiscardOldestPolicy:丢弃最老的任务
  • CallerRunsPolicy:谁提交的任务交给谁去执行(让提交任务的线程去执行)
    在这里插入图片描述

线程池的线程设置为多少比较合适

  • CPU密集型(加密、计算等):最佳线程数为CPU核心的1-2倍
  • 耗时IO型(读写数据库、文件、网络读写):最佳线程数为CPU核心的5-10倍
  • 线程数=CPU核心数*(1+平均等待时间/平均工作时间)

  • 我的公众号:Coding抠腚
  • 一个被电焊耽误的Java程序员。偶尔发发自己最近学到的干货。学习路线,经验,技术分享。技术问题交流探讨。
    Coding抠腚
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章