快速掌握ThreadPoolExecutor--Java多线程并发编程(下编)

ThreadPoolExecutor源码分析(下编)

  • ThreadPoolExecutor源码分析(上编)中,我们知道调用线程池线程执行的方法是execute()和submit()
  • 线程在执行的过程中存在着五种状态的切换,线程的状态以及工作的线程数透过源码分析,知道是由ctl这个属性进行计算得出的
  • 并且对execute()源码进行了分析,那么其中addWorker( )是添加当前线程到工作线程中执行线程,具体又是怎么执行的呢?下面我们接着往下看。

一. 解析添加工作线程的 addWorker方法

  • 主要业务:添加工作线程,并启动工作线程
private boolean addWorker(Runnable firstTask, boolean core) {
    //这个是定义一个内层for循环,跳出外层for循环的一个标识
    retry:
    //这里对线程池“状态”的判断,以及对工作线程“数量”的判断,成功则添加失败则执行再次尝试操作
    for (;;) {
        // 获取ctl值
        int c = ctl.get();
        // 拿到线程池的状态
        int rs = runStateOf(c);

        // 判断线程池的状态是否为RUNNING(如果不是,再次做后续的判断,查看当前任务是否可以不处理)
        if (rs >= SHUTDOWN &&
          // 如果为SHUTDOWN状态,且满足addwork(null,false),且工作任务队列不为空,则添加工作线程失败
            ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
            return false;
		
        //内层for循环判断工作线程数量
        for (;;) {
            //基于ctl获取当前工作线程数量
            int wc = workerCountOf(c);
            //判断工作线程是否大于最大值
            if (wc >= CAPACITY ||
                //判断工作线程和核心线程或者最大线程数量的大小
                wc >= (core ? corePoolSize : maximumPoolSize))
                //说明当前工作线程已经达到最大值了
                return false;
            //没有达到最大值,则自增工作线程数
            //以CAS的方式,对工作线程+1
            if (compareAndIncrementWorkerCount(c))
                //如果成功则跳出外层for循环
                break retry;
            //当有并发的操作,则需要重新获取ctl的值  
            c = ctl.get();  // Re-read ctl
            //基于新获取的ctl拿到线程池状态,判断和之前的rs状态是否一致
            if (runStateOf(c) != rs)
                //说明并发操作导致线程池的状态变化,需要重新判断状态
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }

    //添加工作线程,并启动工作线程
    //工作线程是否启动
    boolean workerStarted = false;
    //工作线程是否添加了
    boolean workerAdded = false;
    //worker就是工作线程
    Worker w = null;
    try {
        //构建工作线程,将任务扔到了worker对象中
        w = new Worker(firstTask);
        //获取worker中绑定的Thread线程
        final Thread t = w.thread;
        if (t != null) {
            //加锁...避免添加工作线程的时候,突然把线程执行shutdown操作
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
          		// 基于重新获取的ctl,拿到线程池状态
                // 再次去判断了线程的状态,避免加锁前线程状态被改变)
                int rs = runStateOf(ctl.get());
				// 这里表示如果满足线程状态为RUNNING,就添加工作线程
                if (rs < SHUTDOWN ||
                    // 如果线程池状态为SHUTDOWN,并且传入的任务为null
                    (rs == SHUTDOWN && firstTask == null)) {
                    // 则开始添加线程
                    // 这里是判断当前线程是否为run状态
                    if (t.isAlive()) 
                        throw new IllegalThreadStateException();
                    // 将构建好的work对象添加到workers(其实就是一个Hashset集合)
                    workers.add(w);
                    // 获取工作线程个数
                    int s = workers.size();
                    // 如果现在的工作线程数,大于历史最大的工作线程数
                    if (s > largestPoolSize)
                        // 重新赋值给largestPoolSize
                        largestPoolSize = s;
                    // 最后将工作线程添加,设置为true
                    workerAdded = true;
                }
            } finally {
                // 释放锁
                mainLock.unlock();
            }
            if (workerAdded) {
                //工作线程添加成功,启动线程
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            // 表示,如果启动工作线程失败,调用这个方法来对添加工作线程失败补救
            // (可理解为回滚到没添加线程之前的操作)
            addWorkerFailed(w);
    }
    return workerStarted;
}

二. Worker对象(实际上就是工作线程)

private final class Worker 
    extends AbstractQueuedSynchronizer  // 处理线程中断的情况
    implements Runnable{				// 存储需要执行的任务

    private static final long serialVersionUID = 6138294804551838833L;

    // 工作线程对象,初始化时候构建出来的
    final Thread thread;
    // 需要执行的任务
    Runnable firstTask;

    Worker(Runnable firstTask) {
        setState(-1); // 设置状态为-1,是为了让工作线程不被中断
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

    // 当调用t.start(),执行当前的run方法
    public void run() {
        runWorker(this);
    }

    // 中断线程不是立即让线程停止,只是将thread的中断标识设置为true
    
    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) {
            }
        }
    }
}

三. runWorker方法(实际上就是执行任务的流程)

  • 主要业务:执行任务的流程,并且做了中断线程的相关判断
final void runWorker(Worker w) {
    // 当前工作线程
    Thread wt = Thread.currentThread();
    // 拿到worker对象中封装的任务
    Runnable task = w.firstTask;
    // 然后将firstTask设置为null
    w.firstTask = null;
    // 将worker的stater设置为0,让其可以被中断
    w.unlock();
    boolean completedAbruptly = true;
    try {
        // 获取任务的两种方式:
        // 1. 执行execute/submit时,传入的任务直接处理
        // 2. 从工作队列中获取任务执行
        while (task != null || (task = getTask()) != null) {
            // 加锁,在SHUTDOWN状态下,当前线程不允许被中断
            w.lock();
            // 如果线程池状态变为了STOP状态,必须将当前线程中断
            // 第一个判断:判断当前线程池状态是否为STOP
            // 第二个判断:查看中断标记是否为false,如果为false,说明不是STOP;
            // 如果为true,则需要再次查看是否并发操作导致线程为STOP
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) 
				// 查询当前线程中断标记是否为false
                // 是false, 则执行wt.interrupt();
                && !wt.isInterrupted())
                
                // 将中断标记设置为true
                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
                task = null;
                // 执行成功的任务个数+1
                w.completedTasks++;
                // 将state标记位设置为0
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

四. getTask方法

  • getTask( ) 其实就是从工作队列 workQueue 中获取到任务的方法
private Runnable getTask() {
    // 这只是个标识(非核心线程可以干掉)
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        //========判断线程池状态========

        // 获取ctl标识
        int c = ctl.get();
        // 拿到线程池的状态
        int rs = runStateOf(c);

        // 如果满足条件,则工作线程减一
        // 第一个判断:线程状态为SHUTDOWN,STOP,且线程池状态大于等于STOP
        // 第二个判断:线程池状态为SHUTDOWN,且工作队列为空
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            //移除当前工作线程
            decrementWorkerCount();
            // 返回null,交给processWorkerExit移除当前线程
            return null;
        }

        //========判断工作线程数量========

        // 获得工作线程数
        int wc = workerCountOf(c);

        // allowCoreThreadTimeOut 表示是否允许核心线程超时(默认为false)
        // 第二个判断是:工作线程是否大于核心线程 
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 工作线程数是否已经大于最大线程数
        // 工作线程数大于核心线程数,并且当前线程已经超时,则去除当前线程
        if ((wc > maximumPoolSize || (timed && timedOut))
            // 如果工作线程数大于1,或者工作队列为空,则去除当前线程
            && (wc > 1 || workQueue.isEmpty())) {
            // 基于CAS方式去除线程
            if (compareAndDecrementWorkerCount(c))
                // 返回null,交给processWorkerExit移除当前线程
                return null;
            continue;
        }

        //========从工作队列中获取任务========

        try {
            Runnable r = timed ?
                // 阻塞到一定时间,从工作队列拿任务
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            // 一直阻塞
            workQueue.take();
            if (r != null)
                // 如果拿到任务直接返回执行
                return r;
            // r为空,说明从队列中获取任务超时了
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

五. processWorkerExit方法

  • 移除当前工作线程的操作
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) 
        // 进入这个方法,主要是因为线程执行异常(勾子函数)引起的
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 记录当前线程池一共处理了多少个任务
        completedTaskCount += w.completedTasks;
        // 移除工作线程 
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
	
    // 尝试将线程池关系(转换到过渡状态-->销毁状态 )
    tryTerminate();

    int c = ctl.get();
    // 当前线程池状态说明是RUNNING,SHUTDOWN
    if (runStateLessThan(c, STOP)) {
        // 如果是正常状态则移除当前工作线程
        if (!completedAbruptly) {
            // 核心线程数最小值
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 如果工作队列中的任务不为空,设置工作线程最小值为1
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 如果工作线程在线程池中,则直接返回
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 程序走到这里,说明是不正常的方式移除了工作线程,则需要添加个空任务的工作线程去执行
        addWorker(null, false);
    }
}

以上是我们对ThreadPoolExecutor的核心源码进行分析,了解了线程是怎么去添加线程,移除线程,以及执行线程的流程,那么现在回到开头,一起回答下这几个问题?

思考

1. ThreadPoolExecutor线程池大小设置?

  • 线程池的理想大小取决于被提交任务的类型以及所部署系统的特性。

  • 线程池应该避免设置的过大或过小,如果线程池过大,大量的线程将在相对很少的CPU和内存资源上发生竞争,这不仅会导致更高的内存使用量,而且还可能耗尽资源。

  • 如果线程池过小,那么将导致许多空闲处理器无法执行任务,降低了系统吞吐率。

  • 因此需要分析任务的特性,去决定设置线程池的大小:

    任务的性质分为:CPU密集型,IO密集型,混合型

    任务等级分为:高,中,低

    任务执行时间分为:长,中,短

    任务的依赖性分为:是否依赖其他系统资源,如数据库链接等。

  • 根据性质不同的任务可以交给不同规模的线程池去执行

    CPU密集型任务:应配置尽可能小的线程,如配置CPU个数+1的线程数

    IO密集型任务:应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1

    混合型的任务:如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了。

    若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大, 才能更好的利用CPU。

  • 有个估算线程池大小合理值的公式

    最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 ) CPU数目*

    比如:平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。

    这个公式进一步转化为:最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1) CPU数目*

**可以得出一个结论:**线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。

2. 阻塞队列怎么设置比较合适?

通过源码了解知道阻塞队列实现类(常用的)有:

ArrayBlockingQueue 是一个由数组结构组成的有界阻塞队列,是先进先出的原则进行排序。

LinkedBlockingQueue是一个由链表结构组成的有界阻塞队列,如果不制定大小,默认是Interger.MAX_VALUE, 先进先出原则。

SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。线程池中用这个队列,必须有一个线程可以执行队列中获取的任务。

DelayQueue是一个支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以制定多久才能从队列中获取当前元素。只有在时间到时,才能从队列中获取元素。

通过了解其特性,根据实际任务的应用场景进行选择即可。

参考文献:[ThreadPoolExecutor线程池大小设置](https://www.6miu.com/read-2402603.html)

以上内容是在【马士兵教育】源码剖析深入讲解Java多线程并发实现原理系统教程的学习总结,感兴趣的同学可以去B站搜索,了解一下哦!!!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值