美文网首页
android 多线程 — 线程池原理

android 多线程 — 线程池原理

作者: 前行的乌龟 | 来源:发表于2019-04-26 15:46 被阅读0次
    好邪恶...

    日常使用线程池基本都是使用 Executors 提供给我们设计好的各色线程池对象了,我们点进去看看:

        public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
        }
    
        public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
        }
    
        public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
        }
    
        public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
            return new ScheduledThreadPoolExecutor(corePoolSize);
        }
    

    可以看到3个都是使用了 ThreadPoolExecutor 这个类,1个用的是 ScheduledThreadPoolExecutor 这个类,他们的区别是各自的参数不同,那么我们要研究线程池的实现就得看取去看 ThreadPoolExecutor 这个类了

    ThreadPoolExecutor、ScheduledThreadPoolExecutor 这2个类都是实现了 ExecutorService 这个线程池的接口,而 ExecutorService 又继承了 Executor 这个更深层次的接口,看下 UML 类图:


    1. ThreadPoolExecutor 构造方法

    ThreadPoolExecutor 这个类的构造方法上篇文章有说过,可以自己设置参数从而实现自己的线程调度器,这里我再放一下

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

    没什么好说的,大家可以重点关注下 Executors 各个工厂方法中传入的参数有何不同,如何实现自己的意图的

    2. ThreadPoolExecutor.execute 方法

    ThreadPoolExecutor.execute() 是线程池的核心,也是研究线程池原理的不二入口

        public void execute(Runnable task) {
            //取出当前线程池活跃的线程数。
            //ctl是一个原子类型的对象(final AtomicInteger ctl),用来保存当前线程池的线程数以及线程池的状态。
            int c = ctl.get();
            //如果当前的活跃线程数小于核心线程数,即使现在有空闲线程,也创建一个新的线程,去执行这个任务
            if (workerCountOf(c) < corePoolSize) {
                //创建一个新的线程,去执行这个任务。
                if (addWorker(task, true))
                    return;
                //如果执行到这一句说明任务没有分配成功。
                //所以获得当前线程池状态值,为后面的检查做准备。
                c = ctl.get();
            }
            //如果大于核心线程数,检查一下线程池是否还处于运行状态,并尝试把任务放入到blockingQueue任务队列中。
            if (isRunning(c) && workQueue.offer(task)) {
                //这里再次检查一下线程池的状态
                int recheck = ctl.get();
                if (! isRunning(recheck) && remove(task))
                    //如果线程池不处于运行状态的话,就把我们刚才添加进任务队列中的任务移出,并拒绝这个任务。
                    reject(task);
                //检查如果当前线程池中的线程数,如果为0了,就为线程池创建新线程(因为有可能之前存活的线程在上一次检查过后死亡了)
                else if (workerCountOf(recheck) == 0)
                    addWorker(null, false);
            }
            //执行到这一句,说明队列满了。这时,如果当前线程池中的线程数还没有超过最大线程数,就创建一个新的线程去执行这个任务,如果失败就拒绝这个任务。
            else if (!addWorker(task, false))
                reject(task);
    }
    

    我在代码里加上了注解,这样看着舒服,看到没 ThreadPoolExecutor 是如何添加任务的,启动新线程的

    代码中的亮点我觉得就是 if 判断了,我们写 if 时至少是我自己都是往上罗各种状态,没有其他的扩展,但是大家看到没,官方代码里面运用了 &&、|| 的特性,&& 2个都是 true 才是 true ,前面的要是 false 了,后面的就不用运行了,比如 if (isRunning(c) && workQueue.offer(task)) ,先判断状态,不是的话直接走下面,是的话走后面的条件,源码这里直接就进行了添加队列的操作,操作成功进入里面,不成功还是走外面,是不是很巧妙,这样节省了很多代码哎,其实熟悉下的话也不会觉得逻辑都多复杂

    我们来说说期中的执行逻辑:

    线程池的策略是线程数 = 核心线程数了,优先把任务添加到阻塞队列里, 如果队列满了或是添加失败才尝试启动新线程执行任务,因为什么设计,是因为手机现在都是多核的,可以同时运行复数的线程,但是线程数要是超过 cpu 核心数的话,就会造成线程竞争 cpu 时间,来回切换线程会造成性能上的巨大损失,对于非 IO 型的高计算密度任务来说这是得不偿失的,还不如大家排队等着快呢

    执行流程
    线程池是如何实现循环的

    答案就在于线程池自己线程类型了 Worker 了,Worker 是线程池堆线程的封装,但是 Worker 并不是继承 Thread 的,而是实现了 Runnable 接口,其成员变量有个 thread 对象

        private final class Worker implements Runnable{
    
           final Thread thread;
            Runnable firstTask;
            volatile long completedTasks;
    
            Worker(Runnable firstTask) {
                setState(-1); // inhibit interrupts until runWorker
                this.firstTask = firstTask;
                this.thread = getThreadFactory().newThread(this);
            }
      }
    

    大家想啊 Worker 既然是实现的 Runnable 接口,那么名堂自然就在 run 方法里了

    
        public void run() {
            runWorker(this);
        }
    
        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);
                        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);
            }
        }
    

    答案在 while (task != null || (task = getTask()) != null) 这个循环里,这里又是非常巧妙的运用了 || 的特性,前面的是 true 后面的条件就不知行了,前面不是 true 才执行后面的条件,firstTask 不是 null 直接进入里面执行,firstTask 执行完了会不停的 getTask 获取任务,这里就循环的跑起来了

        private Runnable getTask() {
            boolean timedOut = false; // Did the last poll() time out?
    
            for (;;) {
                // Check if queue empty only if necessary.
                if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                    decrementWorkerCount();
                    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 {
                   // 这里通过 time 判断是不是核心线程
                   // 非核心线程就 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 任何循环跑来起获取任务就 ok 了

    相关文章

      网友评论

          本文标题:android 多线程 — 线程池原理

          本文链接:https://www.haomeiwen.com/subject/yopfnqtx.html