线程池

前言

       在java中,如果每个请求到达就创建一个新线程,那么创建和销毁线程所花费的时间是非常巨大的,如果创建过多的线程,会使内存使用过多或者线程切换过于频繁导致系统资源不足。如果使用线程池,事先创建好一定的线程,用一个容器维护,每次任务进来就从池里面获取一个线程,任务执行完就把线程还给线程池。

(1)降低创建线程和销毁线程的开销。

(2)可以提高响应速度,当有新任务进来的时候不需要重新等待创建线程。

(3)合理设置线程池的大小可以避免因为线程数超过硬件资源瓶颈带来的问题。

常用API

    为了方便使用线程池,java提供了几种创建线程池的工厂方法,都是基于ThreadPoolExecutor来构建的。这就你不需要了解太多ThreadPoolExecutor的知识就可以使用线程池了。
    public ThreadPoolExecutor(int corePoolSize,//核心线程数
                              int maximumPoolSize,//最大线程数
                              long keepAliveTime,//超时时间,超过核心线程数量以为的线程 多长时间进行空闲线程回收
                              TimeUnit unit,//存活时间单位
                              BlockingQueue<Runnable> workQueue,//保存执行任务的队列
                              ThreadFactory threadFactory, //创建新线程使用的工厂
                              RejectedExecutionHandler handler) {    //当任务无法执行的时候的处理方式
        ...
    } 
    1,newFixedThreadPool:返回一个固定大小的线程池,当有任务进来,分配一个线程给它执行,如果没有空闲线程,则会暂存在一个任务队列中,等待空闲线程再执行。
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    } 
    2,newSingleThreadExecutor:创建一个线程的线程池,若空闲则执行,否则暂存到任务队列中。
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    } 
    3,newCachedThreadPool:返回一个可伸缩的线程池(0到Integer.MAX_VALUE),不限制最大线程数,若有空闲线程则执行任务,若无任务则不创建线程,并且每一个空闲线程会在60秒后启动回收。
    //可以看出来核心线程数为0
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    } 
    4,newScheduledThreadPool:创建一个指定数量的线程池,但是这个线程池还带有延迟和周期性执行任务的功能,类似定时器。
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
                  new DelayedWorkQueue(), threadFactory);

线程池原理分析

    ctl的作用:线程池中ctl贯穿整个线程池中,主要用来保存线程数量和线程池的状态,一个 int 数值是 32 个 bit 位,这里采用高 3 位来保存运行状态,低 29 位来保存线程数量。-1 的二进制是 32 个 1(1111 1111 1111 1111 1111 1111 1111 1111),左移29位高三为是 111。
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;
    //接收新任务,并执行队列中的任务,初始化的时候处于这个状态
private static final int RUNNING    = -1 << COUNT_BITS;
    //不接收新任务,但是执行队列中的任务,调用shutdown()时
private static final int SHUTDOWN   =  0 << COUNT_BITS;
    //不接收新任务,不执行队列中的任务,但是中断正在执行的任务,调用shutdownNow()时
private static final int STOP       =  1 << COUNT_BITS;
    //所有的任务都已结束,线程数量为 0,处于该状态的线程池即将调用 terminated()方法向terminated状态转变
private static final int TIDYING    =  2 << COUNT_BITS;
    //terminated()方法,执行完成
private static final int TERMINATED =  3 << COUNT_BITS;
    执行流程图
 
    1,execute(Runnable )
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();//读取ctl变量
        if (workerCountOf(c) < corePoolSize) {//当前线程数和corePoolSize比较,当小于时:
            if (addWorker(command, true))//addWorker一个new thread来处理该任务(true的情况),直接返回;如果addWork返回false(线程池被shutdown or shutdownNow;或者同时又别的客户端提交任务,并且此时线程数大于corePoolSize);继续往下执行
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {//对addWorker返回false的情况进行判断,当线程池还运行着,说明是因为thread number 大于corePoolSize的情况,则&&操作第二个表达式把任务添加到workQueue队列中.
            int recheck = ctl.get();//再次读取ctl,防止并发哦
            if (! isRunning(recheck) && remove(command))//把任务加入队列的同时,pool被shutdown, 则从队列中删除task,并且调用rejectHandler的方法
                reject(command);//如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务
            else if (workerCountOf(recheck) == 0)//如果此时线程数为0(即设置了allowCorePoolSizeTimeOut为true的情况),则追加一个new thread(初始任务为null)
                addWorker(null, false);
        }       
        else if (!addWorker(command, false))//对于其他addWorker()为false的情况,即!isRunning和workQueue.offer()失败的情况,再次尝试addWorker()添加new thread,如果为false,说明pool被关闭或者达到pool饱和,直接reject。
            reject(command);
    } 
    2,addWorker(Runnable ,boolean )
        a,for循环,通过CAS操作来将线程数加1
        b,新建一个线程并启用
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:    //goto语句
        for (;;) {
            int c = ctl.get();//添加new thread前,还是先读取pool ctl变量,判断一个pool state
            int rs = runStateOf(c);
            //如果线程处于非运行状态,并且 (rs 不等于 SHUTDOWN 或者 firstTask 不等于空 或者 workQueue 为空),直接返回 false(表示不可添加 work 状态)。
            //1,线程池已经 shutdown 后,还要添加新的任务,拒绝
            //2,(第二个判断)SHUTDOWN 状态不接受新任务,但仍然会执行已经加入任务队列的任务,所以当进入 SHUTDOWN 状态,而传进来的任务为空,并且任务队列不为空的时候,是允许添加新线程的,如果把这个条件取反,就表示不允许添加 worker
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) { //自旋
                int wc = workerCountOf(c);//获得 Worker 工作线程数
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))//通过 cas 来增加工作线程数,如果 cas 失败,则直接重试
                    break retry;
                c = ctl.get();  //再次获取 ctl 的值
                if (runStateOf(c) != rs) //这里如果不相等,说明线程的状态发生了变化,继续重试
                    continue retry;
            }
        }
        //上面这段代码主要是对 worker 数量做原子+1 操作,下面的逻辑才是正式构建一个 worker
        boolean workerStarted = false;//工作线程是否启动的标识
        boolean workerAdded = false;//工作线程是否已经添加成功的标识
        Worker w = null;
        try {
            w = new Worker(firstTask);//构建一个 Worker,这个 worker 是什么呢?我们可以看到构造方法里面传入了一个 Runnable 对象
            final Thread t = w.thread;//从 worker 对象中取出线程
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    int rs = runStateOf(ctl.get());
                    //只有当前线程池是正在运行状态,[或是 SHUTDOWN 且 firstTask 为空],才能添加到 workers 集合中
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) //任务刚封装到 work 里面,还没 start,你封装的线程就是 alive,几个意思?肯定是要抛异常出去的
                            throw new IllegalThreadStateException();
                        workers.add(w);//将新创建的 Worker 添加到 workers 集合中
                        int s = workers.size();
                        //如果集合中的工作线程数大于最大线程数,这个最大线程数表示线程池曾经出现过的最大线程数
                        if (s > largestPoolSize)
                            largestPoolSize = s;//更新线程池出现过的最大线程数
                        workerAdded = true;//表示工作线程创建成功了
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {//如果 worker 添加成功
                    t.start();//启动线程
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);//如果添加失败,就需要做一件事,就是递减实际工作线程数(还记得我们最开始的时候增加了工作线程数吗)
        }
        return workerStarted;//返回结果
    }
    3,new Worker(firstTask)
    Worker类继承了AQS,并实现了Runnable接口,有firstTask和thread属性,firstTask代表当前任务,thread是通过ThreadFactory来创建的线程,是用来处理任务的线程。
    Worker(Runnable firstTask) {
        setState(-1); //初始状态 -1,防止在调用 runWorker(),也就是真正执行task前中断thread
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); //因为Worker本身实现了Runnable,所以它本身就是一个Runnable也就是一个线程,所以可以使用this,所以一个线程启动的时候会调用它的run()方法
    }
    public void run() {
        runWorker(this);
    }
    4,addWorkerFailed(Worker )
    如果添加Worker并且启动失败,则会做失败后的处理,主要做两件事:
        a,如果worker已经构建好了,则从workers中移出这个worker
        b,原子递减核心线程数(因为在addWorker方法中先做了原子增加)
        c,尝试结束线程池
    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            tryTerminate();
        } finally {
            mainLock.unlock();
        }
    } 
    5,runWorker(Worker )
    当start之后,会调用Worker类中的run方法,它里面有个runworker方法。它主要做下面几个事:
        a,如果 task 不为空,则开始执行 task
        b,如果 task 为空,则通过 getTask()再去取任务,并赋值给 task,如果取到的 Runnable 不为空,则执行该任务
        c,执行完毕后,通过 while 循环继续 getTask()取任务
        d,如果 getTask()取到的任务依然是空,那么整个 runWorker()方法执行完毕
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock();//unlock,表示当前 worker 线程允许中断,因为 new Worker 默认的 state=-1,此处是调用Worker 类的 tryRelease()方法,将 state 置为 0,而 interruptIfStarted()中只有 state>=0 才允许调用中断
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {////注意这个 while 循环,在这里实现了 [线程复用] // 如果 task 为空,则通过getTask 来获取任务
                w.lock(); //1,上锁,不是为了防止并发执行任务,为了在 shutdown()时不终止正在运行的 worker。2,线程池为 stop 状态时不接受新任务,不执行已经加入任务队列的任务,还要中断正在执行的任务。所以对于 stop 状态以上是要中断线程的,(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)确保线程中断标志位为 true 且是 stop 状态以上,接着清除了中断标志,!wt.isInterrupted()则再一次检查保证线程需要设置中断标志位
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);//这里默认是没有实现的,在一些特定的场景中我们可以自己继承 ThreadpoolExecutor 自己重写
                    Throwable thrown = null;
                    try {
                        task.run();//执行任务中的 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,需要再通过 getTask()取) + 记录该 Worker 完成任务数量 + 解锁
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            //1.将入参 worker 从数组 workers 里删除掉;
            //2.根据布尔值 allowCoreThreadTimeOut 来决定是否补充新的 Worker 进数组workers
            processWorkerExit(w, completedAbruptly);
        }
    }
    6,getTask()
    怎样判断线程有多久没有活动了,是在线程从工作队列 poll 任务时,加上了超时限制,如果线程在 keepAliveTime 的时间内 poll 不到任务,那我就认为这条线程没事做,可以干掉了,看看这个代码片段你就清楚了。
    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            //对线程池状态的判断,两种情况会 workerCount-1,并且返回 null。 1,线程池状态为 shutdown,且 workQueue 为空(反映了 shutdown 状态的线程池还是要执行 workQueue 中剩余的任务的)2,线程池状态为 stop(shutdownNow()会导致变成 STOP)(此时不用考虑 workQueue的情况)
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;//返回 null,则当前 worker 线程会退出
            }

            int wc = workerCountOf(c);

            // timed 变量用于判断是否需要进行超时控制。
            //allowCoreThreadTimeOut 默认是 false,也就是核心线程不允许进行超时;
            // wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;对于超过核心线程数量的这些线程,需要进行超时控制
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //1,线程数量超过 maximumPoolSize 可能是线程池在运行时被调用了 setMaximumPoolSize()被改变了大小,否则已经 addWorker()成功不会超过 maximumPoolSize。2, timed && timedOut 如果为 true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时.其实就是体现了空闲线程的存活时间。
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                //根据 timed 来判断,如果为 true,则通过阻塞队列 poll 方法进行超时控制,如果在keepaliveTime 时间内没有获取到任务,则返回 null.
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;//如果 r==null,说明已经超时了,设置 timedOut=true,在下次自旋的时候进行回收
            } catch (InterruptedException retry) {
                timedOut = false;// 如果获取任务时当前线程发生了中断,则设置 timedOut 为false 并返回循环重试
            }
        }
    }
    7,拒绝策略

        AbortPolicy:直接抛出异常,默认策略;

        CallerRunsPolicy:用调用者所在的线程来执行任务;

        DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;

        DiscardPolicy:直接丢弃任务;

    当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

线程池的注意事项

1,阿里开发手册不建议使用线程池:使用Executors来创建线程池使得用户不需要关心创建线程池的参数配置,这会导致我们可能因为不了解不同的线程池里面的参数配置而导致一些问题,例如newFixdThreadPool 或者singleThreadPool允许的队列长度为Integer.MAX_VALUE,如果使用不当可能会造成线程堆积引发OOM。

2,合理配置线程池的大小

CPU密集型,执行计算任务,响应时间很快,cpu一直在运行,配置过多的线程反而导致cpu频繁切换上下文。所以建议线程数配置为 cpu核心数+1

IO密集型,主要是执行IO操作,一般cpu处于空闲状态,导致cup利用不高。所以建议线程数配置为cpu核心数的2倍,公式:线程池设定最佳线程数目 = ((线程池设定的线程等待时间+线程 CPU 时间)/线程 CPU 时间 )* CPU 数目

3,线程池中线程的初始化:创建线程池之后,默认是没有线程的,需要提交任务才有线程,如果想创建成功立即创建线程可以调用prestartCoreThread()或者prestartAllCoreThreads()

4,线程池的关闭:shutdown(),不接收新任务,但执行完缓存队列中的任务。shutdownNow(),不接收新任务,立即终止线程池,尝试打断正在执行的任务,并且清空缓存队列。

5,缓存池容量调整:setCorePoolSize() 和 setMaximumPoolSize()

6,线程池的监控:重写线程池的 beforeExecute、afterExecute 和 shutdown 等方式对线程池监控

Callable/Future使用及原理分析

    线程池中execute和submit区别:execute可以接收Runnable参数,submit可以接收Runnable和Callable两种类型。所以对与Callable可以得到一个Future的返回值。

    RunnableFuture继承了Runnable和Future接口,Runnable都知道,Future代表了一个任务的生命周期,并提供了一些相关的方法,取消任务的执行,获取任务的执行结果,判断任务是否完成或者取消。

    如果把runnable比作生产者,future比作消费者,那么futuretask就是两者的共享,生产者运行run执行任务,消费者运行get获取结果。

   FutureTask

    1,state的含义
    private volatile int state;
    private static final int NEW          = 0;//NEW 新建状态,表示这个 FutureTask还没有开始运行
    private static final int COMPLETING   = 1;// COMPLETING 完成状态, 表示 FutureTask 任务已经计算完毕了, 但是还有一些后续操作,例如唤醒等待线程操作,还没有完成。
    private static final int NORMAL       = 2;// FutureTask 任务完结,正常完成,没有发生异常
    private static final int EXCEPTIONAL  = 3;// FutureTask 任务完结,因为发生异常。
    private static final int CANCELLED    = 4;// FutureTask 任务完结,因为取消任务
    private static final int INTERRUPTING = 5;// FutureTask 任务完结,也是取消任务,不过发起了中断运行任务线程的中断请求
    private static final int INTERRUPTED  = 6;// FutureTask 任务完结,也是取消任务,已经完成了中断运行任务线程的中断请求
    2,run()
    //运行call方法,拿到返回值根据有没有异常设置setException(ex)和set(result)
    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    3,get方法
    //1,判断当前线程状态,如果小于COMPLETING,说明还有任务还没有执行完全,调用awaitDown让当前线程等待
    //2,返回执行结果或者抛出异常
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
    4,awaitDone(boolean, long)
    //如果当前线程没有执行完,则把当前线程加入等待队列
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;    //节点是否已经添加
        for (;;) {
            if (Thread.interrupted()) {
                removeWaiter(q);    //如果中断标志位是true,移出当前线程抛出异常
                throw new InterruptedException();
            }

            int s = state;
            if (s > COMPLETING) {//大于COMPLETING,证明已经结束
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) //等于COMPLETING,说明还有后续的一些线程执行,让出cpu执行权
                Thread.yield();
            else if (q == null) //到这状态肯定是new了,那么就需要将当年线程阻塞等待
                q = new WaitNode();//等待节点为空 新建一个
            else if (!queued)
                //通过cas将新节点加入到列表中
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {// timed 为 true 表示需要设置超时
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            }
            else
                LockSupport.park(this);
        }
    } 
    5,report
    private V report(int s) throws ExecutionException {
        Object x = outcome;//表示 call 的返回值
        if (s == NORMAL)// 表示正常完结状态,所以返回结果值
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    } 

    在线程池中的运行

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
    发现相比于execute多了一个封装RunnableFuture的一步,然后调用execute方法。
 



posted @ 2020-05-06 17:14  gnice512  阅读(138)  评论(0编辑  收藏  举报