JUC并发编程-线程池之ThreadPoolExecutor使用和原理

1. 介绍

JUC并发编程-线程池之ThreadPoolExecutor使用和原理_第1张图片Executor :运行新任务的简单接口,将任务提交和任务执行细节解耦
ExecutorService:具备管理执行器和任务生命周期的方法,提交任务机制更完善

2. 作用

  • 提升性能 :它们通常在执行大量异步任务时,由于减少了每个任务的调用开销,并且它们提供了一种**限制和管理资源(包括线程)**的方法,使得性能提升明显;
  • 统计信息:每个ThreadPoolExecutor保持一些基本的统计信息,例如完成的任务数量。

3. 参数:

  • corePoolSize 核心线程池大小

  • maximumPoolSize 最大线程池大小

    线程池会根据corePoolSize和maximumPoolSize自动调整线程池的大小

    规则:

      1. 当正在执行的线程数量少于corePoolSize的时候,即使存在线程空闲,也会创建一个新的线程来处理新的任务。
      2. 如果当前正在运行的线程大于等于corePoolSize但小于maximumPoolSize,且等待队列不满,则将新的任务压入等待队列,否则如果队列已满,则创建新的线程来处理新任务。
      3. 如果此时当前正在执行的线程数等于maximumPoolSize,则执行拒绝策略
    

JUC并发编程-线程池之ThreadPoolExecutor使用和原理_第2张图片

通过执行流程我们可以得出以下总结:

  • 当设置corePoolSize和maximumPoolSize相同,可以创建一个固定大小的线程池

  • 设置maximumPoolSize为一个极大的值,即创建了一个可以容纳任意数量的并发任务

    注意:通常这两个参数仅在构建时设置,但也可以通过set方法动态设置

  • keepAliveTime 线程最大空闲时间

  • TimeUnit unit 时间单位

    ​ 如果线程池当前拥有超过corePoolSize的线程,那么多余的线程在空闲时间超过keepAliveTime时会被终止。这提供了一种在不积极使用线程池时减少资源消耗的方法。

    注意:如果池子变得活跃,则可以通过set方法动态调整
    ​ keep-alive策略仅适用于存在超过corePoolSize线程的情况。 但是,只要keepAliveTime值不为零,方法allowCoreThreadTimeOut(boolean)也可用于将此超时策略应用于核心线程

  • BlockingQueue 等待队列

    BlockingQueu用于存放提交的任务,当队列已满时,则执行拒绝策略

  • ThreadFactory 线程创建工厂

    ​ 新线程使用ThreadFactory创建。 如果未另行指定,则使用Executors.defaultThreadFactory默认工厂,使其全部位于同一个ThreadGroup中,并且具有相同的NORM_PRIORITY优先级和非守护进程状态。

    ​ 通过提供不同的ThreadFactory,您可以更改线程的名称,线程组,优先级,守护进程状态等。

  • RejectedExecutionHandler 拒绝策略

    拒绝任务有两种情况:1. 线程池已经被关闭;2. 任务队列已满且maximumPoolSizes已满;

重要方法:

  • prestartCoreThread 核心线程预启动

在默认情况下,只有当新任务到达时,才开始创建和启动核心线程,但是我们可以使用 prestartCoreThread()prestartAllCoreThreads() 方法动态调整。
如果使用非空队列构建池,则可能需要预先启动线程。

  • Hook methods 钩子方法

​ ThreadPoolExecutor为提供了每个任务执行前后提供了钩子方法,重写beforeExecute(Thread,Runnable)afterExecute(Runnable,Throwable)方法来操纵执行环境; 例如,重新初始化ThreadLocals,收集统计信息或记录日志等。此外,terminated()在Executor完全终止后需要完成后会被调用,可以重写此方法,以执行任殊处理。

4. 线程池状态

  • RUNNING:能接受新提交的任务,并且也能处理阻塞队列中的任务
  • SHUTDOWN:不再接受新提交的任务,但可以处理存量任务
  • STOP∶不再接受新提交的任务,也不处理存量任务
  • TIDYING:所有的任务都已终止
  • TERMINATED : terminated()方法执行完后进入该状态
    JUC并发编程-线程池之ThreadPoolExecutor使用和原理_第3张图片

5. 重要方法

  • prestartCoreThread 核心线程预启动

    在默认情况下,只有当新任务到达时,才开始创建和启动核心线程,但是我们可以使用 prestartCoreThread()prestartAllCoreThreads() 方法动态调整。
    如果使用非空队列构建池,则可能需要预先启动线程。

  • Hook methods 钩子方法

    ​ ThreadPoolExecutor为提供了每个任务执行前后提供了钩子方法,重写beforeExecute(Thread,Runnable)afterExecute(Runnable,Throwable)方法来操纵执行环境; 例如,重新初始化ThreadLocals,收集统计信息或记录日志等。此外,terminated()在Executor完全终止后需要完成后会被调用,可以重写此方法,以执行任殊处理。

  • shutdown()方法

  • shutdownNow()方法

6. Executors线程工具

Executors:线程池工厂

  • newFixdThread(int nThreads):指定工作线程数量的线程池,核心线程数和最大线程数相等:使用LinkedBlockingQueue,容量无界
  • newCachedThreadPool():处理大量短时间工作任务的线程池:使用了SynchronousQueue,而最大线程数设为最大
    (1)试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;
    (2)如果线程闲置的时间超过阈值,则会被终止并移出缓存;
    (3)系统长时间闲置的时候,不会消耗什么资源
  • newSingleThreadExecutor():核心线程数和最大线程数都设为1
    创建唯一的工作者线程来执行任务,如果线程异常结束,会有另一个线程取代它
  • newSingleThreadScheduleExecutor()和newScheduledThreadPool(int corePoolSize)
    定时或者周期性的工作调度,两者的区别在于单一工作线程还是多个线程
  • newWorkStrealingPool()
    内部会构建ForkJoinPool,利用working-stealing算法,并行地处理任务,不保证处理顺序

7. ThreadPool原理

7.1. 重要的属性
7.1.1. 容量与线程池状态的设计

ThreadPoolExecutor将线程池的线程的个数与线程池的状态融合到一个int型的整型数中,其中高29位表示线程的数量,低三位表示状态

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
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;
7.1.2. 线程集合 HashSet< Worker > workers
private final HashSet<Worker> workers = new HashSet<Worker>();

Worker是线程的一个内部类,其设计为

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
    
    private static final long serialVersionUID = 6138294804551838833L;

    final Thread thread;//工作的线程
    
    Runnable firstTask;//初始化时的第一个任务,可能是null
    
    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);//代理,执行的是runWorker{被定义在外部类中}
    }

    // Lock methods
    //
    // The value 0 represents the unlocked state.
    // The value 1 represents the locked state.
    protected boolean isHeldExclusively() {
        return getState() != 0;
    } 
    
    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(); }

    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}
7.2. 重要方法
7.2.1. 提交任务 submit and execute

ThreadPoolExecutor的父接口定义了多个接口以适应不同的需求

其中包括两大类:

  • submit 为了适应存在返回值Callable接口,提供了具有返回值的执行接口
  • execute 无返回值接口,适应Ruunable接口

实际上:Callable接口的实现之所以能够实现能获取返回值,是因为Callable接口最终被包装Runnable接口的实现类(FutureTask),因此submit方法实际上仍然是将Callable接口封装为Runnable类,进而交给executor去执行。

//ExecutorService定义了三个提交任务的接口
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
//ExecutorService的父接口Executor定义的接口
void execute(Runnable command);

实现方法

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    //包装成RunnableFuture(Runnbale子类),实际上就是FutureTask
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);//交给execute执行
    return ftask;
}

execute

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
    int c = ctl.get();
    //1. 如果少于核心线程数,则会尝试  创建新的线程,并将此任务当作第一个任务;addWorker的调用以原子方式不断检查runState和workercount,并通过返回false来防止在不应该添加线程时产生的假警报。
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();//当线程数量达到了阈值,则addWorker返回false,进行下面的处理
        /*
        	下面的处理存在两种情况:
        	1.添加到任务队列
        	2.尝试创建>核心线程数的线程
        */
    }
    //如果正在运行 则将任务提交到工作队列中;如果成功入队,则进行第二次检擦
    //检查是否应该添加一个线程(因为现有线程在Last检查之后已经死亡),或者池在进入该方法后关闭。因此,我们重新检查状态,如果有必要,则在停止排队时回滚排队;如果没有线程,则启动一个新线程。
    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);//仍在在运行,但是没有线程了(可能是考虑核心线程数为0的情况),则启动一个新线程。
    }
    //3.如果没有正在运行或者工作队列已经满了无法提交新的任务,则ch尝试创建新的worker来执行线程。
    else if (!addWorker(command, false))
        reject(command);
}
7.2.2. 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;//在core的情况,如果超过限度则返回false,表示不能继续添加
            if (compareAndIncrementWorkerCount(c))
                break retry;//增加 线程数量 成功退出
            c = ctl.get();  // 失败则重新获取state 
            if (runStateOf(c) != rs)//如果state发生了改变,则进行下一次循环
                continue retry;
        }
    }
	//如果程序来到这,说明此时任务已经获得了创建新线程并执行任务的资格。
    boolean workerStarted = false;//任务线程是否已经开始执行任务
    boolean workerAdded = false;//线程是否已经被添加到线程池
    Worker w = null;
    try {
        w = new Worker(firstTask);//新建worker,实际上已经新建了一个线程
        final Thread t = w.thread;//获取新建的线程
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());

                if (rs < SHUTDOWN ||//线程池子处于运行状态
                    (rs == SHUTDOWN && firstTask == null)) {//线程池虽然处于Shutdown状态,但是firsttask为null。说明存在任务在shutdown之前提交到了queue,但是已经没有线程执行了,则可以在此处创建一个新的线程去执行
                    if (t.isAlive()) 
                        throw new IllegalThreadStateException();
                    workers.add(w);//将worker加入到线程池中
                    int s = workers.size();
                    if (s > largestPoolSize)//largestPoolSize 跟踪最大达到的池大小。仅在mainLock下访问。
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();//启动线程
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}
7.2.3. 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 {
        //如果任务不为null,则执行任务
        //否则 获取任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // 如果池子被停止,则确保线程被打断;如果没有,则确保线程不被打断;这需要一个重新检查,第二次是为了当清除打断之后来处理shutdownNow方法
            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);
    }
}
7.2.4. getTask 获取任务
private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

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

        //只在必要时检查队列是否为空。
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();//清除所有的worker
            return null;
        }
        
        int wc = workerCountOf(c);
        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;
        }
    }
}

你可能感兴趣的:(java)