线程池原理

文章目录

  • ThreadPoolExecutor
  • 线程池的组成(Executor 框架)
  • Java 线程池工作过程
  • 拒绝策略
  • 线程池实现原理
    • execute 方法执行过程分析
      • execute 方法执行逻辑
      • addWorker 方法
    • Worker 类
      • runWorker 方法
      • getTask 方法
      • processWorkerExit
  • 如何合理配置线程池的大小

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。它的主要特点为: 线程复用;控制最大并发数;管理线程。

ThreadPoolExecutor

ThreadPoolExecutor 的构造方法如下:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, 
                          TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 
            Executors.defaultThreadFactory(), defaultHandler);
}

构造方法中每个参数的含义:

  1. corePoolSize: 指定了线程池中的线程数量。

  2. maximumPoolSize: 指定了线程池中的最大线程数量。

  3. keepAliveTime: 当前线程池数量超过 corePoolSize 时,多余的空闲线程的存活时间,即多少时间内会被销毁。

  4. unit: keepAliveTime 的单位。

  5. workQueue: 任务队列,被提交但尚未被执行的任务。

  6. threadFactory: 线程工厂,用于创建线程,一般用默认的即可。

  7. handler: 拒绝策略,当任务太多来不及处理,如何拒绝任务。
    线程池原理_第1张图片

线程池的组成(Executor 框架)

一般的线程池主要分为以下 4 个组成部分:

  1. 线程池管理器:用于创建并管理线程池

  2. 工作线程:线程池中的线程

  3. 任务接口:每个任务必须实现的接口,用于工作线程调度其运行

  4. 任务队列:用于存放待处理的任务,提供一种缓冲机制

Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor ,Callable 和 Future、FutureTask 这几个类。

Executor 框架包括 3 大部分:

1) 任务(Runnable / Callable)
执行任务需要实现的 Runnable 接口 或 Callable接口。Runnable 接口或 Callable 接口 实现类都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。

2) 任务的执行(Executor)
如下图所示,包括任务执行机制的核心接口 Executor ,以及继承自 Executor 接口的 ExecutorService 接口。ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 这两个关键类实现了 ExecutorService 接口。

3) 异步计算的结果(Future)
Future 接口以及 Future 接口的实现类 FutureTask 类都可以代表异步计算的结果。
线程池原理_第2张图片

Java 线程池工作过程

线程池原理_第3张图片

  1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。

  2. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:

    a) 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;

    b) 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入任务队列;

    c) 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;

    d) 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会按照拒绝策略来执行。

  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。

  4. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

拒绝策略

线程池中的线程已经用完了,无法继续为新任务服务,同时,任务队列也已经排满了,再也塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。

JDK 内置的拒绝策略如下:

  1. AbortPolicy : 直接抛出异常,阻止系统正常运行。

  2. CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

  3. DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。

  4. DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。

以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际需要,完全可以自己扩展 RejectedExecutionHandler 接口。

线程池实现原理

每一个 Thread 的类都有一个 start 方法。 当调用 start 启动线程时 Java 虚拟机会调用该类的 run 方法。 那么该类的 run() 方法中就是调用了 Runnable 对象的 run() 方法。 我们可以继承重写 Thread 类,在其 start 方法中添加不断循环调用传递过来的 Runnable 对象。 这就是线程池的实现原理。循环方法中不断获取 Runnable 是用 Queue 实现的,在获取下一个 Runnable 之前可以是阻塞的。

execute 方法执行过程分析

在分析之前,先了解几个重要的成员变量和基本方法

// ctl 是一个原子整数,代表线程池控制状态,其中包含两个概念字段
// 1、runState:线程池运行状态
// 2、workerCount:池中线程的有效数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// Integer 整数的高3位代表线程池的运行状态,剩余29位代表池中线程的有效数量
private static final int COUNT_BITS = Integer.SIZE - 3;

// workCount 的限制数量 (2^29)-1
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;


// 线程池的5种状态
// 高3位是111, 接受新任务并处理排队任务, 注意这是一个负数
private static final int RUNNING    = -1 << COUNT_BITS;

// 高3位是000, 不接受新任务, 但处理排队任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;

// 高3位是001, 不接受新任务, 也不处理排队任务, 以及中断正在进行的任务
private static final int STOP       =  1 << COUNT_BITS;

// 高3位是010, 所有的任务都已终止, workerCount为0, 线程转换到状态TIDYING, 将运行terminate()钩子方法
private static final int TIDYING    =  2 << COUNT_BITS;

// 高3位是011, terminated() has completed
private static final int TERMINATED =  3 << COUNT_BITS;

// 获取运行状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 获取池中线程数量
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 计算ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }

execute 方法执行逻辑

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
	// 获取原子整数 ctl
	int c = ctl.get();
	// 线程池中线程数量小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
    	// 添加新的 worker 线程, 并运行任务, 成功则结束
    	// 参数为 true 则使用 corePoolSize 作为边界,否则使用 maximumPoolSize
        if (addWorker(command, true))
            return;
        // 失败则重新获取 ctl
        c = ctl.get();
    }
    // 线程池中线程数量大于等于核心线程数或者添加新的 worker 失败
    // 线程池处于 RUNNING 状态, 且任务添加到队列成功
    if (isRunning(c) && workQueue.offer(command)) {
    	// 再次获取 ctl
        int recheck = ctl.get();
        
        // 如果此时线程池不是 RUNNING 状态, 则移除刚刚添加到队列中的任务
        if (! isRunning(recheck) && remove(command))
        	// 移除成功后, 对该任务执行拒绝策略
            reject(command);
        // 如果线程池处于 RUNNING 状态, 且线程数量为 0
        else if (workerCountOf(recheck) == 0)
        	// 添加一个没有任务的 worker 线程, 该线程自动从任务队列获取任务
            addWorker(null, false);
    }
    // 任务添加到队列失败, 说明任务队列已满, 且线程池中线程数量大于等于核心线程数
    // 此时创建非核心线程
    else if (!addWorker(command, false))
    	// 创建线程失败执行拒绝策略
        reject(command);
}

addWorker 方法

addWorker 方法:根据当前池状态和给定的界限(核心或最大值)添加新 worker

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    // 外层循环
    for (;;) {
    	// 获取线程池运行状态
        int c = ctl.get();
        int rs = runStateOf(c);

        // 如果 rs > SHUTDOWN (STOP、TIDYING、TERMINATED)则直接拒绝接受任务
        
        // 如果 rs == SHUTDOWN
        // 		如果 firstTask != null 则拒绝接受任务
        // 		如果 firstTask == null 且任务队列为空, 则拒绝接创建新的 worker线程
        
        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

		// 内层循环
        for (;;) {
        	// 获取线程池中线程数量
            int wc = workerCountOf(c);
            
            // 如果为 true 使用 corePoolSize 作为边界, 否则使用 maximumPoolSize
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // CAS 操作增加 workerCount 数量, 成功跳出外层循环
            if (compareAndIncrementWorkerCount(c))
                break retry;
            // 如果增加 workerCount 失败,则重新获取 ctl 的值
            c = ctl.get();  // Re-read ctl
            
            // 如果运行状态改变, 则返回外层循环继续执行, 否则自旋增加 workerCount 
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    // 增加 workerCount 成功

	// 创建 worker 线程, 并添加到池中
	// 如果成功, 则启动 worker 线程
	// 如果失败, CAS 自旋操作 workerCount 减一, 并且如果池中添加了 worker, 则从池中移除它
	
	// worker 线程启动标记
    boolean workerStarted = false;
    // worker 线程添加到池中的标记
    boolean workerAdded = false;
    Worker w = null;
    try {
    	// 创建 worker 线程(该 worker对象内部维护着一个线程, 所以称该对象为 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 rs = runStateOf(ctl.get());

                // rs < SHUTDOWN 代表线程池处于 RUNNING
                // 如果 rs == SHUTDOWN && firstTask == null, 则表明任务队列不为空
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    
                    // 如果该线程处于活跃状态, 直接抛异常
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 将 worker线程添加到线程池中
                    workers.add(w);
                    int s = workers.size();
                    // 跟踪达到的最大池大小
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    // worker 线程成功添加到池中, 标记为 true
                    workerAdded = true;
                }
            } finally {
            	// 释放锁
                mainLock.unlock();
            }
            // 成功添加到池中
            if (workerAdded) {
            	// 启动线程
                t.start();
                // 线程启动成功标记为 true
                workerStarted = true;
            }
        }
    } finally {
    	// 线程启动失败
        if (! workerStarted)
        	// CAS 自旋操作 workerCount 减一, 并且如果池中添加了 worker, 则从池中移除它
            addWorkerFailed(w);
    }
    return workerStarted;
}

Worker 类

Worker 类部分源码

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
{
    final Thread thread;
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;

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

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

    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }
}

Worker 类继承了 AQS,使用 AQS 来实现独占锁的功能,并且是不可重入的。

Worker 类实现了 Runnable 接口

因此,在构造方法中this.thread = getThreadFactory().newThread(this);,当创建线程时传进去的是 this,所以,当线程启动时,便会调用 Worker 类中的 run 方法。

runWorker 方法是主要的 worker 运行循环,主要工作是重复地从队列获取任务并执行它们,同时处理一些问题,下面我们来具体看一下 runWorker 方法

runWorker 方法

该方法是 ThreadPoolExecutor 类中的方法

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();
            
            // 1. 如果线程池正在停止,那么要保证当前线程是中断状态;
			// 2. 如果不是的话,则要保证当前线程不是中断状态
			// Thread.interrupted()调用该方法会清除中断标志位
            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);
    }
}

这里的 beforeExecute 方法和 afterExecute 方法在 ThreadPoolExecutor 类中是空的,留给子类来实现。

runWorker 方法的执行过程:
1、while 循环不断地通过 getTask() 方法获取任务;
2、调用 task.run() 执行任务;
3、如果 task为 null 则跳出循环,执行 processWorkerExit 方法;

getTask 方法

private Runnable getTask() {
	// timeOut变量的表示上次从阻塞队列中获取任务时是否超时
    boolean timedOut = false; // Did the last poll() time out?

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

        // 1. 线程池处于 STOP状态
        // 2. 线程池处于 SHUTDOWN状态且队列是空的
        // 在以上这两种状态下, 返回 null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // timed变量用于判断是否需要进行超时控制
        // allowCoreThreadTimeOut 默认为false, 如果设置为 true, 则超时后会终止核心线程
        // wc > corePoolSize, 非核心线程超时后会终止
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 如果获取任务超时了或者 wc > maximumPoolSize
        // 		此时判断如果任务队列不为空则必须保留一个 worker 线程
        // 		如果任务队列为空线程池中的线程数量可以减为 0
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            // CAS 自旋减一, 成功则返回 null
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
        	// poll方法, 如果在 keepAliveTime时间内没有获取到任务, 则返回 null;
        	// take方法, 会一直阻塞
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            // 返回获取到的任务
            if (r != null)
                return r;
            // 获取任务超时
            timedOut = true;
        } catch (InterruptedException retry) {
        	// 获取任务时发生异常, 重置 timedOut, 并重新获取任务
            timedOut = false;
        }
    }
}

getTask 方法返回 null 时,在 runWorker 方法中会跳出 while 循环,然后会执行 processWorkerExit 方法。

processWorkerExit

对于将要挂掉的 worker 线程进行清理和记账

private void processWorkerExit(Worker w, boolean completedAbruptly) {
	// 如果异常标志位为 true时, 则 workerCount需要 CAS自旋操作减一
	// 如果为 false, 说明线程执行时没有发生异常, 在 getTask 方法返回 null时, 已经减一了
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    	// //统计完成的任务数
        completedTaskCount += w.completedTasks;
        // 从线程池中移除 worker线程
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

	// 根据线程池状态进行判断是否结束线程池
    tryTerminate();
    int c = ctl.get();
    
    // 当线程池是 RUNNING或 SHUTDOWN状态时, 如果 worker是异常结束, 那么会直接 addWorker;
    // 如果 allowCoreThreadTimeOut = true, 并且等待队列有任务, 至少保留一个worker;
    // 如果 allowCoreThreadTimeOut = false, workerCount不少于 corePoolSize。
    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);
    }
}

至此,processWorkerExit 执行完之后,工作线程被销毁,以上就是整个工作线程的生命周期。

从 execute 方法开始,Worker 使用 ThreadFactory 创建新的工作线程,runWorker 通过 getTask 获取任务,然后执行任务,如果 getTask 返回 null,进入processWorkerExit 方法,整个线程结束,如图所示:
线程池原理_第4张图片

如何合理配置线程池的大小

  • CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
  • I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。

参考
https://www.cnblogs.com/warehouse/p/10720781.html

你可能感兴趣的:(多线程)