线程池(ThreadPoolExecutor JDK1.7)

平常我们经常都会使用到线程池,但是有没考虑过为什么需要使用线程池呢?下面我列举一下问题,大家可以思考一下
1.当前服务器的硬件环境是多少核的CPU,它和线程的关系又是什么?
2.jvm能创建多少个线程?
3.多线程主要解决什么问题?
4.你使用线程池的目的是什么?

以上几个问题都是帮助你更好的使用java的线程(还可以衍生更多的小问题,如:jvm维护线程的消耗,cpu调度线程的消耗,应该使用多少个线程才能最大化利用多核CPU..)。答案需要自己去百度,我这也讲不好,反而误导大家。

线程池顾名思义就是,存储了N多线程的一个池子,该池子维护了线程的创建、销毁,线程的创建数量,任务调度..
通常我们使用线程池,都是通过Executors 工厂方法来创建一个线程池。该工厂方法主要提供了以下几大类型线程池的创建:
1.CacheThreadPool
这是一个线程数变动性非常强的线程池,默认配置下,它可以开启无限多个线程(Integer.maxSize 和 JVM允许线程数范围内)。且如果该线程池里的线程在60秒内如果是处于空闲状态(即没任务执行),那么该线程就会被回收,不再由线程池维护。如果有新任务进来时,由于之前的线程池里的线程已被回收,那么新的线程也会再次创建。当执行完任务,60秒内依旧无新任务的可执行话,那么该线程又会被再次回收。

综合该线程池的特性,我们可以思考下什么情况下应该使用这类线程池。比如:我们的应用服务器上面,会在非固定时间(时间跨域度会尽可能大)和非固定的任务数量。



2.FixedThreadPool
FixedThreadPool一个固定数量的线程池,且该线程池不会随着任务的变化而增多或减少线程数量。即该线程池下的线程池如果你不主动调用销毁shutdowm、purge之类的方法。那么这些线程将会永远被线程池维护着。

3.SingleThreadExecutor
SingleThreadExecutor是一个固定单线程的线程池,该线程池会永远都保持着一个线程的活动状态,如果该线程池的单线程因某些异常而退出后,线程池会继续创建一个新的线程。

4.ScheduledThreadPool
ScheduledThreadPool是一个支持任务定时调度的线程池。

以上四种线程池,除了ScheduledThreadPool,其他三种都是通过创建一个ThreadPoolExecutor对象来实现的,只是在构造该对象时候,初始化该内部的成员变量值不同,所以造就了其他三种类型的线程池的存在。下面看看ThreadPoolExecutor都有些什么成员变量,他们又分别有什么意义?
以下是ThreadPoolExecutor的构造方法之一,而Executors都是通过该构造方法来创建其他三种不同的线程池的。

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

/**
在说这些参数之前,需要先了解下线程池内部的一些工作机制。
线程池ThreadPoolExecutor会自动调节线程池的大小(看ThreadPoolExecuroe.getPoolSize),依据corePoolSize参数的范围大小和maximumPoolSize。
当一个新任务通过ThreadExecutor.execute或者submit被提交时,
    如果当前线程池中的线程数量小于所设定的corePoolSize,一个新的线程则会被创建来处理该请求,即使这些核心的线程池是空闲的(当前没有处理任何任务)。
    如果当前线程池中的线程数量等于所设定的corePoolSize,那么新提交的任务则不会再创建新的线程,而是把该任务放进线程池所维护的任务队列workQueue中。当其他线程执行完了任务那么则会从任务队列中持续的获取任务来执行。
只有当前线程池中的数量超过了corePoolSize但是小于maximumPoolsize,且当queue满了才会去创建新线程(该创建的新线程必须小于maximumPoolsize)来处理该请求。

以下是该构造方法的参数说明:
corePoolSize : 线程池的核心线程数。该参数并不是初始化时候写死了,线程池对象构造完成以后也能通过它提供的一些方法动态修改,ThreadPoolExecutor.setCorePoolSize。

maximumPoolSize : 线程池的最大线程数,只有当任务队列满了才会创建新的线程。该参数并不是初始化时候写死了,线程池对象构造完成以后也能通过它提供的一些方法动态修改,ThreadPoolExecutor.setMaximumPoolSize。

keepAliveTime : 线程池所维护的线程的活动时间。如果超过了该时间范围,而线程还是空闲的,那么该线程将会被回收。不再由线程池所维护。以下是官方的一些说明:
如果当前线程池数大于核心线程池数,且这些线程是空闲的超过所设定的存活时间keepAliveTime,那么这些多出来的线程则会被终止,可以降低线程资源消耗。通过设置尽可能大的过期时间来阻止空闲线程被销毁,可手动调用setKeepAliveTime来设置。默认情况下,该策略只能适用于大于核心线程数的线程,但是可以通过设置ThreadPoolExecutor.allowCoreThreadTimeOut(boolean),则该策略也适用于核心线程数。

unit : 和上面keepAliveTime所对应,表示活动时间的时间单位。如 毫秒、秒、分..

workQueue : 初始化一组任务,但是该组任务并不会马上执行,需要手动调用prestartCoreThread或者prestartAllCoreThreads来预先启动线程才会执行这些初始化的任务。否则只有当你提交第一个任务时候他才会执行,且可能是单线程执行(取决于你提交几次任务)..

threadFactory : 传入一个线程工厂。通过该线程工厂可以创建线程。它创建的所有线程都是通过同样的ThreadGroup和同样的NORM_PRIORITY和non-daemon状态。通过提供的不同ThreadFactory,可以掌握修改线程名字,线程组,优先级,守护状态等。如果线程池调用线程工厂创建一个线程失败时,则返回一个null。且executor会继续,但是可能不会执行任何任务。
如果我们自己重写封装了一遍线程工厂,还有个好处就是可以通过该线程工厂实例维护所有由它创建的线程。

上次我在群里看到一个群友说,他去面试,面试官问他如何判断线程池中一个指定线程当前是否在执行任务,你们觉得可以吗?
**/


上面介绍完了线程池内部参数的意义,下面说下线程池还提供了一些什么外部方法供开发者使用?
Hook methods
提供了一些钩子方法如:ThreadPoolExecutor.beforeExecute and ThreadPoolExecutor.afterExecute。这些方法可在任务执行前后被执行。场景:重新初始化ThreadLocal。收集统计信息,添加日志记录。。

Queue maintenance
ThreadPoolExecutor.getQueue允许访问工作队列来完成监视和调试,但强烈不建议开发者使用它来完成其他目的。

上面说明了线程池的一些构造参数和外部方法,了解了这些后我们可以直接使用ThreadPoolExecutor来构建适合我们的线程池,而不再需要Executors。当然,通过Execturos创建的线程池再配合上面说的一些动态设置参数的方法,也能起到一些很好的效果。



准备开始解刨核心源码了!

先看看成员变量:
参数runstate
RUNNING:接收新任务和处理队列任务
SHUTDOWN:不接收新任务,但处理队列里的任务
STOP:不接收新任务,也不处理队列任务何中断正在执行的任务
TERMINATED:和STOP一样
以下是runstate状态的转换过程:
RUNING->SHUTDOWN 调用shutdown()
(RUNNING or SHUTDOWN) ->STOP 调用shutdownnow()
SHUTDOWN->TERMINATED 当队列和线程池都是空的
STOP->TERMINATED 当线程池是空的

workQueue:
该成员变量是个阻塞队列,主要用于存储需要执行任务和把任务转交给worker线程

mainLock:
用来对成员变量pooSize,corePoolSize,maximumPoolSize,runState、workers修改时候同步

termination:
用来支持waitTemination

workers:
集合包含的所有worker线程在线程池,只能通过mainLock访问

keepAliveTime:
设置空闲线程等待多时毫秒被回收

allowCoreThreadTimeOut:
设置核心线程空闲时候是否被回收,默认false

corePoolSize:核心线程数,需要使用mainLock同步更新

maximumPoolSize:最大线程数,需要使用mainLock同步更新

poolSize:当前线程数,需要使用mainLock同步更新

handler:当线程池饱和或者shutdown时候被执行

threadFactory: 线程工厂

largestPoolSize: 最大线程数,这个参数的存在似乎是为了更正并发情况下poolSize的修改

completedTaskCount:获取已完成的任务数量

defaultHandler:默认的拒绝处理器

addWorker失败的话可能会导致线程池关闭

通常我们把任务通过submit或者execute提交到线程池中,然后接着就什么都不用干,等待线程池帮我们执行任务了。那么线程池内部是如何执行这些任务的呢?它又是如何维护线程和任务的关系?
下面先看一幅关于线程池内部运作的图:

从图中可以看出Worker实现了Runnable接口且封装了线程(注意它在通过线程工厂创建线程时候把this,即Worker传入到Thread的构造参数)
在代码上是线程池维护了一组Worker,通过Worker.thread.start()后,那么Worker所封装的线程就会运行Worker自身的run实现。最后在run实现里持续的从workQueue中获取任务来执行,由于workQueue是阻塞队列,所以如果队列中没有任务,那么该线程则会被挂起等待唤醒(有任务过来),下面从源码中看看:

/**
Worker类里封装了Thread和任务Runnable,还有completedTasks。可以注意到创建一个Thread时候把this传入,这样的话如果我调用Worker.thread.start()就相当于该线程会执行Worker里的run方法了。completedTasks是用来记录该线程完成了多少个任务(非整个线程池)。

注意该Worker继承了AQS同步基础器,它主要实现了互斥锁的功能,但是这个互斥锁和ReentrantLock有点不同,该实现是不允许线程重入获取锁的。下面说说为什么要实现锁功能和非重入:
1.lock方法主要用在标明当前线程正在执行任务中,而private interruptIdleWorkers 方法需要使用tryLock来判断当前线程是否正在执行任务,如果非执行任务状态则表明可能是正在获取任务,那么该线程属于空闲状态,可以被中断。
2.看回答1可以知道这通过ReentrantLock也能实现,但是如果我们在提交一个任务给线程池(实现一个Runnable),如果该
任务里面调用了和interruptIdleWorkers相关的,需要中断当前可获取锁(代表空闲)的线程。如果我们不使用非重入锁,这个任务线程就会给中断,从而导致一些奇怪的问题。

**/
private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /**
         主要看这里,其他的都是关于AQS的实现,具体的可以看AQS源码+我的AQS源码分析篇。

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

        // 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) {
                }
            }
        }
    }

看看Worker run的实现方法:

final void runWorker(Worker w) {
        /**
           这里获取当前执行线程,就是Worker所封装的Thread(因为是通过该Thread启动的,然后执行自身的run方法)
        **/
        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 ((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 {
            //执行到这里代表该线程已被终止,将被回收(从线程池的workers里删除该线程)。
            //这个方法同时也代表了当线程超出了空闲时间后,将不再由线程池维护,而是被GC回收。具体可以看   
            //getTask。由于getTask是以阻塞方式从阻塞队列获取任务,可以通过阻塞获取时候设定一个阻塞时间
            //来达到 keepAliveTime空闲功能。   
            processWorkerExit(w, completedAbruptly);
        }
    }

下面看看getTask是如何实现 线程池维护线程的keepAliveTime功能的。

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

        retry:
        for (;;) {
            int c = ctl.get();
            //获取当前线程池的状态(这部分具体最后讲)
            int rs = runStateOf(c);

            // 如果线程池已被shutdown或者由于其他原因关闭,那么则终止该线程,返回null,最后就会走
            //processWorkerExit方法 了
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            boolean timed;      // Are workers subject to culling?

            for (;;) {
                //获取线程池当前的线程数(worker数量则代表线程数)
                int wc = workerCountOf(c);
                //判断是否需要采取设置 阻塞时间的方式获取任务.如果核心线程也需要空闲回收或者当前线程数
                //量已经超越了核心线程数,那么都需要采取阻塞时间获取任务方式。
                timed = allowCoreThreadTimeOut || wc > corePoolSize;
                //判断是否需要跳出循环,循环仅仅只是为了cas修改减少线程池的线程数。
                if (wc <= maximumPoolSize && ! (timedOut && timed))
                    break;
                // 执行到这里代表阻塞获取任务超时,keepAlivetime时间到了。该线程将被回收
                if (compareAndDecrementWorkerCount(c))
                    return null;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }

            try {
                //如果需要采用阻塞形式获取,那么就poll设定阻塞时间,否则take无限期等待。
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

看了上面知道了线程池最核心的Worker是如何持续处理任务队列中的任务,和keepAlivetimed回收空闲线程。下面再看看execute一个任务后,线程池的处理步骤。比如核心线程数和最大线程数的体现,线程启动等

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        int c = ctl.get();
        //判断当前线程池的线程数是否少于核心线程数,只要少于核心线程数都会addWorker创建一个新Worker(新线程)
        //来处理新任务
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //当前线程数大于核心线程数或者addWroker失败,需要把任务提交到任务队列,等待Worker线程空闲后处理
        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是false)
        //那么这里再次调用addWroker创建新线程(这时创建的线程是maximumPoolSize)。
        //如果还是提交任务失败则调用reject处理失败任务
        else if (!addWorker(command, false))
            reject(command);

    }

    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;
                //cas修改增加线程池所拥有的线程数
                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
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            final ReentrantLock mainLock = this.mainLock;
            //创建一个新的Worker,则封装了一个firstTask
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int c = ctl.get();
                    int rs = runStateOf(c);

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //提交Worker到线程池所维护的workers集合中(可以认为这是一组线程)
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //启动线程,执行Worker执行的run实现
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }


线程池关于 线程和任务之间这部分核心源码已经讲了,下面不再说源码,说说线程池还给我们提供了一些什么方法,平常比较少用到的。
setRejectedExecutionHandler(RejectedExecutionHandler) ,主要是设定当任务提交或者处理失败的后继处理。我们可以通过自定义实现一个RejectedExecutionHandler,当任务提交失败时候可以重试,或者记录日志之类的。默认是AbortPolicy实现抛出异常.

allowCoreThreadTimeOut(boolean value) ,动态允许核心线程也被回收。CacheThreadPoolExecutor也实现类似功能,不过它是通过设定核心线程数为0来实现的。这样创建都所有线程都是maximumpoolsized(允许被回收)

getQueue() 用来查看当前有哪些任务正在队列中等待执行

purge() 对于Future类的任务,可以获取所有该类已为取消状态的任务

getPoolSize() 获取当前线程池中的线程数量

getActiveCount() 只获取当前线程池中正在执行任务的线程数量

getTaskCount() 获取线程池已完成(包括正在执行的任务)的任务数量

getCompletedTaskCount() 只获取已完成的任务数量,不包括正在执行的

invokeAny(Collection<. extends Callable> tasks)
提交一组任务,只要任务集合中有任何一个任务完成,那么则返回该任务结果且中断其他正在执行中的任务。使用场景如:现在你有四个磁 盘,你要从这四个磁盘中搜索一个名叫 xx的文件。任何一个盘中搜索到,其他盘就不需要再搜索了。

invokeAll(Collection<. extends Callable> tasks) 执行这组任务, 全部任务都需完成.

最后就是关于该篇源码开头的ctl 值问题了,ctl里面的值包含了两类值,一个是当前的线程池运行状态,一个是线程池的当前线程数量。至于如何理解这里面的逻辑,需要有位运算基础,可以结合以下代码来观察他们的位运算关系

public class X {
    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;

    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }
    private static int workerCountOf(int c)  { return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }


    public static void main(String[] args) {
        System.out.println("COUNT_BITS:"+COUNT_BITS+"   "+Integer.toBinaryString(COUNT_BITS));
        System.out.println("CAPACITY:"+CAPACITY+"   "+format(Integer.toBinaryString(CAPACITY)));
        System.out.println("RUNNING:"+RUNNING+"   "+format(Integer.toBinaryString(RUNNING)));
        System.out.println("SHUTDOWN:"+SHUTDOWN+"   "+format(Integer.toBinaryString(SHUTDOWN)));
        System.out.println("STOP:"+STOP+"   "+format(Integer.toBinaryString(STOP)));
        System.out.println("TIDYING:"+TIDYING+"   "+format(Integer.toBinaryString(TIDYING)));
        System.out.println("TERMINATED:"+TERMINATED+"   "+format(Integer.toBinaryString(TERMINATED)));
        System.out.println(runStateOf(TIDYING));
        System.out.println(format(Integer.toBinaryString(~CAPACITY)));
    }

    private static String format(String str){
        //if(str!=null) return str;

        char[] cs = str.toCharArray();
        StringBuilder sb = new StringBuilder();
        int j =0;
        for(int i=(cs.length-1); i>=0 ; i--){
            sb.append(cs[j++]);
            if(i % 4 ==0){
                sb.append(" ");
            }
        }
        return sb.toString();

    }

}

这里写图片描述

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值