九、详解线程池ThreadPool

核心模型

九、详解线程池ThreadPool_第1张图片

 有图可知,线程池的核心组成部分分别为:线程列表和阻塞队列。

1、线程列表:这个很容易理解,就是一堆线程,用一个列表存储起来。每次都复用列表中的线程来执行任务,而不需要重新创建新的线程。

2、阻塞队列:为什么需要一个阻塞队列呢,普通的队列行不行呢?  之所以阻塞队列,主要的原因是平衡【放任务】和【拿任务】之间的速率。

        如果【放任务】的速度快,但是【拿任务】的速度慢,那么就需要一个队列来存储多余的任务。

        如果【拿任务】的速度快,但是【放任务】的速度慢,那么就需要线程等待新的任务,因此就需要一个阻塞的地方,让线程等待。

因此通过对上述两点的分析,需要的是一个有阻塞功能的队列,BlockingQueue。

模型分析

下面对上述的模型进行分析:

1、阻塞队列:在线程池中,阻塞队列都需要什么属性,需要什么功能呢?

        首先,需要一个list来存储相应的任务对象。

        第二,需要存储一把锁,因为【放任务】和【拿任务】可能有多个线程在同时执行

        第三,需要等待条件,【放任务】的线程等待【队列非满】,而【拿任务】的线程等待【队列非空】

        第四,需要一个get任务的方法,和一个put任务的方法。这个两个方法应该是互斥加锁的,并且在不同的条件下等待。详细可参考:五、详解ReentrantLock-CSDN博客

2、线程列表:线程列表就是一个list类型,每个元素就是一个线程。当然为了做一下特殊逻辑,可以用Thread的父类来进行封装。二、线程创建与运行-CSDN博客

上述的模型为线程池的核心模型,在真正生产环境中,需要根据不同的需求在核心模型的基础上增加其他的功能,例如核心线程数,最大线程数,最大队列长度,等待时间,拒绝策略,线程工厂等等。

那么下面我们开始介绍在生产环境中,真正使用的几种线程池。

ThreadPoolExecutor源码分析

线程池状态

在具体讲解源码之前,先介绍一下ThreadPoolExecutor中的ctl变量。ctl是一个原子整数(AtomicInteger),它同时包含了线程池的运行状态和线程池中工作线程的数量。

其高三位为当前线程池的状态,低29位为当前线程池中的线程数。在TheadPoolExecutor中调用ctlOf方法将 状态和个数拼接起来。

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

那么为什么ThreadPoolExecutor要用一个int变量来表示两个概念,而不是用两个int变量来分别表示线程池状态和线程个数呢?

其主要原因为了并发考虑,因为线程池状态和线程数量在一个int中,只需要一次cas就能将两个含义同时原子的更新。如果用两个变量,需要保证两个变量的一致性。

下面分析一下整个线程池的状态,线程池的状态分为一下几种:
九、详解线程池ThreadPool_第2张图片

running : 当线程池处于running状态的时候,用户可以向线程池中添加新任务,并且线程正常处理阻塞队列中的任务。

shutdown:当线程处于shutdown状态的时候,用户不能像线程池中添加新的任务了,但是线程依然能够将队列中的任务执行完毕

stop:当线程处于stop状态的时候,用户无法向线程池提交新的任务,并且线程也不会继续执行队列中的任务。

构造方法

先从创建一个ThreadPoolExecutor来观察,线程池都需要什么样的参数。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue 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;
}

1、corePoolSize:核心线程数,意味着当正常情况下,线程池只有corePoolSize个线程在运行

2、maximumPoolSize: 最大线程数,可以理解为救急线程,当线程池中的线程数已经等于corePoolSize,并且阻塞队列中已经满足,那么就继续扩容线程,直到线程数为maximumPoolSize

3、keepAliveTime: 表示那些创建的救急线程需要空转多长时间,如果超过这个时间,救急线程将被回收

4、TImeUnit:时间单位

5、workQueue: 阻塞队列。请看核心模型

5、threadFactory:创建线程的工厂,如果不设置,将会分配一个默认的工DefaultThreadFactory

6、handler:拒绝策略。当核心线程数满了,阻塞队列满足,救急线程也满了,那么当前put任务的线程要执行这个handler,来确认如何处理。

        常见的拒绝策略有:AbortPolicy,CallerRunsPolicy,DiscardOldestPolicy, DiscardPolicy。后面详细分析一下上述四个拒绝策略的代码。

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

1、首先从ctl原子变量中获取值,然后通过workerCountof函数从ctl中获取低29位,计算出线程数。我们之前讲过ctl高三位记录线程池状态,低29位记录线程个数。

private static int workerCountOf(int c)  { 
    return c & COUNT_MASK; 
}

private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;


private static final int COUNT_BITS = Integer.SIZE - 3;

上述代码COUNT_BITS = Integer.SIZE-3。  Integer.SIZE表示Integer类型的大小,即占用几个bit位,所以COUNT_BITS=32-3 = 29。

COUNT_MASK = (1<<29)-1, 即将数字1左移29位,即10000000000(后面29个0),然后再-1,那就是29个1。

因此workerCountOf(c)方法的逻辑就是将c与29个1向&操作,结果就是后29bit的值。

addWorker()

2、之后判断当前线程数是否小于corePoolSize。如果小于,那么执行addWork方法。如果addWorker添加成功,则直接返回,否则,执行下一个if

        我们重点分析addWork方法,这个方法的意思是,添加一个worker,其实本质上就是添加一个线程。

       

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        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 {
            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.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;
    }

这段代码的主要逻辑:

1、先判断线程池的状态,如果是ShutDown或者是Stop则直接添加失败。这个我们之前讲过。

2、然后再次判断当前线程的个数是否已经到达了核心线程数,如果到达了,则直接添加失败

3、之后将线程数利用cas的方式进行加1。八、详解CAS无锁-CSDN博客

4、如果上述都满足, 则执行new Worker(firstTask)。 这段代码就是创建一个线程池。我们看一下worker的源码

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

可以看到,Worker的构造函数中,将当前提交的runnable任务复制给firstTask,然后创建了一个线程。并将this复制给线程,因此我们可以推断,Worker类一定实现了Runnable接口,然后创建的这个线程调用start方法的时候,一定运行了Worker的run方法。也就是说线程池中的线程运行逻辑其实就是Worker的run。

worker的run()方法

那么我们看一下worker的run方法,这个是核心

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);
                    try {
                        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);
        }
    }

这段代码,就是线程池中每个线程真正执行的逻辑:

1、第一步就进入一个while循环,判断firstTask是否为空,如果firstTask不为空,则进行循环体,如果为空,则执行getTask(). 这个方法我们一会讲,这个方法里面涉及到了救急线程是如何销毁的。

2、当task不为空的时候,进入循环体,如果为空,调用finaly,然后当前线程结束。这个finnaly里面的逻辑就是将当前worker从workers队列中删除,因为线程销毁了,worker对象也没用用了。然后尝试终止线程池,如果线程池已经没有工作线程,则不在接收新的任务。

3、如果task不为空,也就是能获取到runnable任务,那么就执行runnable的run方法。当执行完之后,将task赋值为null,之后在此进行while循环,从新获取任务,周而复始。

getTask()方法

接下来看一下worker中,是如何getTask()的

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

        for (;;) {
            int c = ctl.get();//获取线程池ctl

            // 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 {
            //如果线程池超了,那么就利用超时等待的方式区poll,否则就用阻塞的方式。
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true; 
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
}

getTask()的核心逻辑就是判断当前线程池中的线程是否大于核心线程数,或者配置核心线程数可销毁。如果是线程数超过了核心线程数,那么本次线程去队列获取任务的时候,就利用等待超时(这里就用到了keeplivetime),如果没有超过,那么就一直阻塞线程,直到有新的任务被添加到队列。

当addWorker执行完之后,将新的worker加入到worker队列中,也就是核心模型中的线程list中。当然这个步骤是加了lock锁的。之后调用worker的start方法,开始执行逻辑。

下面我们回到execute()方法,如果addWorker()失败,那么就向阻塞队列中插入一个任务。

这里用到了阻塞队里的 queue.offer()方法。这里大概看一下阻塞队列是如何工作的,其实就是利用了lock锁和condition。五、详解ReentrantLock-CSDN博客

以LinkedBlockingDeque为例:
 

public boolean offerLast(E e) {
        if (e == null) throw new NullPointerException();
        Node node = new Node(e); //创建一个node节点
        final ReentrantLock lock = this.lock;
        lock.lock(); //加锁
        try {
            return linkLast(node); //添加元素
        } finally {
            lock.unlock(); //解锁
        } 
    }

而的关键在于linkLast()方法:

 private boolean linkLast(Node node) {
        // assert lock.isHeldByCurrentThread();
        if (count >= capacity) //超过容量,直接失败
            return false;
        Node l = last; //获取尾节点
        node.prev = l;
        last = node;
        if (first == null)
            first = node;
        else
            l.next = node;
        ++count;
        notEmpty.signal(); //释放不为空的信号
        return true;
}

这里可以看到,当向队列加入元素的时候不会阻塞,如果超过容量则直接返回false。只有从队列拿数据的时候才会阻塞。

继续回到execute()方法,当添加队列失败了,那么就继续判断是否超过核心线程数,如果没有,则创建救急线程。

最后,如果救急线程创建失败,则执行reject方法

reject()方法

这个就是在调用拒绝策略:

 final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
 }

其实就是调用一开始构造方法里面传递过来的RejectHandler。

接下来分析一下jdk提供的四种拒绝策略:
1、AbortPolicy:直接抛出异常

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }

2、CallerRunsPolicy:当前调用线程执行runnable任务。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run(); //当前线程直接调用run
            } 
}

3、DiscardPolicy:直接丢弃任务

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        //空的方法体,直接忽略这个任务
}

4、DiscardOldestPolicy:丢弃最早添加的那个任务

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll(); //丢弃队列的头
                e.execute(r); //重新提交改任务
            }
}

你可能感兴趣的:(并发编程,java,开发语言)