Android中的线程池

1 定义

我们在 《Android中线程形态AsyncTask、HandlerThread和IntentService工作原理分析》 中介绍AsyncTask的工作原理提到线程池,其中执行任务的线程池THREAD_POOL_EXECUTOR是一个ThreadPoolExecutor类的对象,串行线程池sDefaultExecutor是一个实现了接口Executor的内部类对象,它们都是线程池。其实Android中的线程池源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。线程池是什么?线程池可以简单理解成线程的缓存池和管理器。一般情况下我们在处理一件耗时的事情时就会创建线程,在事情完成后就会对线程进行销毁,但是线程的创建和销毁都需要开销的成本,如果频繁地创建和销毁就会造成不必要的浪费,所以为了提高线程的使用效率,就可以使用缓存池的策略进行对线程的复用和生命周期的管理,这就是线程池。而且线程池还能有效地控制池内线程最大并发数,避免大量的线程之间因互相抢占资源而导致阻塞以及可以提供定时执行或指定间隔循环执行等功能。

2 Android提供的线程池分类

Android通过Executors类封装好了5类的线程池:FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor以及SingleThreadScheduledExecutor,涵盖大多数的使用场景。

2.1 FixedThreadPool

通过Executors类的newFixedThreadPool方法来创建。其构造方法可以指定线程数量,它是一种数量固定且只有核心线程的线程池,核心线程的意义就是当线程处于空闲状态时也不会被回收,其队列是LinkedBlockingQueue无参数版本,表示任务队列是没有大小限制(Integer.MAX_VALUE的。所以这类线程池比较适合更快速地响应外界的请求。代码如下:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
            nThreads,                                     // 固定核心线程数
            nThreads,                                     // 最大线程数和核心线程数是一样的
            0L,                                           // 无超时,不被回收
            TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>()           // 队列大小是Integer.MAX_VALUE

    );
}

// 使用
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
fixedThreadPool.execute(runnable);

2.2 CachedThreadPool

通过Executors类的newCachedThreadPool方法来创建。它是一种不限数量且只有非核心线程的线程池,线程池中超过60秒闲置线程就会被回收,而且队列SynchronousQueue是无法插入任务的,所以当任务被加入后便会立即执行。这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置被回收后,它是几乎不占用任务系统资源。代码如下:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(
            0,                                  // 无核心线程
            Integer.MAX_VALUE,                  // 最大线程数是任意大
            60L,                                //  60秒超时
            TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>()    // SynchronousQueue是无法插入任务,任务是立即执行的
    );
}

// 使用
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(runnable);

2.3 SingleThreadExecutor

通过Executors类的newSingleThreadExecutor方法来创建。它中一种只有一个核心线程的线程池,其队列是LinkedBlockingQueue也是无参数版本,表示任务队列是没有大小限制的。它确保所有的任务都在同一个线程中按顺序执行。这类线程池比较适合统一所有的外界任务到一个线程中,这使得在这些任务之间不需要处理线程同步的问题。代码如下:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(
                    1,                                  // 固定1个核心线程数
                    1,                                  // 最大线程数和核心线程数是一样的是1个
                    0L,                                 // 超时,不被回收
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>() // 队列大小是Integer.MAX_VALUE
            ));
}

// 使用
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(runnable);

2.4 ScheduledThreadPool

通过Executors类的newScheduledThreadPool方法来创建,newScheduledThreadPool返回的类型是ScheduledExecutorService,ScheduledExecutorService继承于ExecutorService,它内部封装了一些定时和固定周期执行任务的方法,而且队列使用的是DelayedWorkQueue,它是延时阻塞队列。ScheduledThreadPool的构造方法可以指定线程数量,它也是核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置10秒后便会立即回收。这类线程池比较适合用于执行定时任务和具有固定周期的重复任务。代码如下:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize,                 // 固定核心线程数
            Integer.MAX_VALUE,          // 最大线程数是任意大
            DEFAULT_KEEPALIVE_MILLIS,   // 10秒超时
            MILLISECONDS,
            new DelayedWorkQueue()
    );
}

// 使用
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
// 延迟100ms后执行一次
scheduledThreadPool.schedule(runnable, 100, TimeUnit.MILLISECONDS);                
// 延迟100ms后执行一次,然后固定每隔1000ms后重复执行(固定周期)
scheduledThreadPool.scheduleAtFixedRate(runnable, 100, 1000, TimeUnit.MILLISECONDS);   
// 延迟100ms后执行一次,然后等上个任务完成后(每个任务时间不定)再每隔1000ms后重复执行(固定延迟)
scheduledThreadPool.scheduleWithFixedDelay(runnable, 100, 1000, TimeUnit.MILLISECONDS);

2.5 SingleThreadScheduledExecutor

通过Executors类的newSingleThreadScheduledExecutor方法来创建,这是SingleThreadExecutor和ScheduledThreadPool意义上的合体。代码如下:

public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}

// 使用

ScheduledExecutorService scheduledThreadPool = Executors.newSingleThreadScheduledExecutor();
scheduledThreadPool.schedule(runnable, 100, TimeUnit.MILLISECONDS);
scheduledThreadPool.scheduleAtFixedRate(runnable, 100, 1000, TimeUnit.MILLISECONDS); 
scheduledThreadPool.scheduleWithFixedDelay(runnable, 100, 1000, TimeUnit.MILLISECONDS);

3 ThreadPoolExecutor简述

上述提到,FixedThreadPool、CachedThreadPool和SingleThreadExecutor三种类型都是通过ThreadPoolExecutor对象传入不同的参数进行创建的,我们来看看ThreadPoolExecutor:

3.1创建和参数

ThreadPoolExecutor的构造方法提供了一系列参数来配置线程池,源码如下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

corePoolSize

核心线程数,即能够同时执行的任务数量。默认情况下,即使处于闲置状态,核心线程也会在线程池中一直存活。除非将ThreadPoolExecutor的allowCoreThreadTimeOut属性设为true,那么超时策略(keepAliveTime参数)也会使核心线程终止。

maximumPoolSize

线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续的新任务将会被阻塞(包括了核心线程池数量)。

keepAliveTime

线程闲置的超时时长,超过这个时长,线程就会被回收。一般用于非核心线程。(将ThreadPoolExecutor的allowCoreThreadTimeOut属性设为true时会作用于核心线程)。

unit

它是一个枚举,用于指定keepAliveTime参数的时间单位,有毫秒(TimeUnit.MILLISECONDS)、秒、分种,等。

workQueue

线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个参数中。一般地有以下几种可选择:

         LinkedBlockingQueue     链表实现的阻塞队列,可指定容易,如果不指定默认容量Integer.MAX_VALUE表示不限容。

         ArrayBlockingQueue       数组实现的不可自动扩容的阻塞队列。

         SynchronousQueue        无法插入任务的零容量的同步阻塞队列,用于实现生产者与消费者的同步。

         PriorityBlockingQueue   二叉堆实现的优先级阻塞队列。

         DelayQueue                     延时阻塞队列,队列中的元素需要实现Delayed接口,底层使用PriorityQueue的二叉堆对Delayed元素排序。

threadFactory

它是一个接口,用于创建线程的工厂,为线程池提供创建新线程的功能,实现它可以设置创建线程的名称、优先级等属于。默认情况都会使用Executors工具类中定义的默认工厂类DefaultThreadFactory。

handler

这个参数不常用,用于当任务数超过maximumPoolSize时,线程无法执行新任务时,会调用handler的rejectedExecution方法直接抛出RejectedExecutionException异常。若不指定默认是ThreadPoolExecutor.AbortPolicy()。ThreadPoolExecutor提供了4种取值:

         ThreadPoolExecutor.AbortPolicy()                   默认值,表示放弃添加任务并抛出RejectedExecutionException异常。

         ThreadPoolExecutor. CallerRunsPolicy ()       表示放弃添加任务位不抛出异常。

         ThreadPoolExecutor. DiscardPolicy ()             表示放弃队列最前面的任务,然后重新尝试添加新任务,若失败会重复尝试。

         ThreadPoolExecutor. DiscardOldestPolicy ()  表示由调用线程处理任务。

3.2 参数决定执行任务规则

1、如果线程池中的线程数量未达到核心线程的数量(corePoolSize参数的值),那会直接启动一个核心线程执任务;

2、如果线程池中的线程数量已达到或者超过核心线程的数量,那任务会被插入到任务队列中(workQueue参数)排队等待执行;

3、如果在2中因为任务队列也已经满了无法插入任务队列,这时如果线程数量未达到线程池规定的最大值(maximumPoolSize参数的值),那么就会立刻启动一个非核心线程来执行任务;

4、如果在3中线程数量已达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知调用者。

3.4 使用示例

3.4.1 示例1

我们参考AsyncTask源码里的执行任务的线程池THREAD_POOL_EXECUTOR的代码来自定义一个ThreadPoolExecutor使用示例:

public class ThreadPoolManager {
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 3;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };
    private static final RejectedExecutionHandler sRunOnSerialPolicy = new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            Log.e("ThreadPoolManager", "Exceeded ThreadPoolExecutor pool size");

        }
    };

    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAXIMUM_POOL_SIZE,
            KEEP_ALIVE_SECONDS,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>(20),
            sThreadFactory,
            sRunOnSerialPolicy);

    public void execute(@NonNull Runnable runnable) {
        THREAD_POOL_EXECUTOR.execute(runnable);
    }
}
// 调用
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
for (int i = 0; i < 10; i++) {
    threadPoolManager.execute(new TestTask(i));
}

class TestTask implements Runnable {
    private final static String TAG = "TestTask";
    private int mNum;

    public TestTask(int num) {
        super();
        this.mNum = num;
        Log.e(TAG, "task" + mNum + " 准备执行...");
    }

    @Override
    public void run() {
        SystemClock.sleep(100);
        Log.e(TAG, "task" + mNum + " 开始执行...");
        // 模拟耗时执行时间
        SystemClock.sleep(5000);
        Log.e(TAG, "task" + mNum + " 执行结束...");
    }
}

输出结果如下:

E/TestTask: task0 准备执行...
E/TestTask: task1 准备执行...
E/TestTask: task2 准备执行...
E/TestTask: task3 准备执行...
E/TestTask: task4 准备执行...
E/TestTask: task5 准备执行...
E/TestTask: task6 准备执行...
E/TestTask: task7 准备执行...
E/TestTask: task8 准备执行...
E/TestTask: task9 准备执行...

E/TestTask: task4 开始执行...
E/TestTask: task7 开始执行...
E/TestTask: task8 开始执行...
E/TestTask: task1 开始执行...
E/TestTask: task3 开始执行...
E/TestTask: task6 开始执行...
E/TestTask: task2 开始执行...
E/TestTask: task0 开始执行...
E/TestTask: task5 开始执行...

E/TestTask: task7 执行结束...
E/TestTask: task8 执行结束...
E/TestTask: task6 执行结束...
E/TestTask: task0 执行结束...
E/TestTask: task3 执行结束...
E/TestTask: task2 执行结束...
E/TestTask: task1 执行结束...
E/TestTask: task5 执行结束...
E/TestTask: task4 执行结束...

E/TestTask: task9 开始执行...
E/TestTask: task9 执行结束...

上例中,设定了核心线程数等于CPU核心数+1,线程池的最大线程数为CPU核心数的2倍+1,任务队列的容量为20,当前演示的机器CPU核心数是8,所以按照上面提到的ThreadPoolExecutor执行任务时遵循规则2,在执行10个任务中,只有开始9个任务(CPU个数是8个+1个)开始执行,待这9个任务执行结束后再开始剩下1个任务。

3.4.1 示例2

如果,我们修改一下代码,把new LinkedBlockingQueue<Runnable>(20)换成new SynchronousQueue<Runnable>(),会怎样?我们运行程序再来看看。

输出结果如下:

E/TestTask: task0 准备执行...
E/TestTask: task1 准备执行...
E/TestTask: task2 准备执行...
E/TestTask: task3 准备执行...
E/TestTask: task4 准备执行...
E/TestTask: task5 准备执行...
E/TestTask: task6 准备执行...
E/TestTask: task7 准备执行...
E/TestTask: task8 准备执行...
E/TestTask: task9 准备执行...

E/TestTask: task0 开始执行...
E/TestTask: task1 开始执行...
E/TestTask: task2 开始执行...
E/TestTask: task3 开始执行...
E/TestTask: task4 开始执行...
E/TestTask: task5 开始执行...
E/TestTask: task6 开始执行...
E/TestTask: task7 开始执行...
E/TestTask: task8 开始执行...
E/TestTask: task9 开始执行...

E/TestTask: task0 执行结束...
E/TestTask: task2 执行结束...
E/TestTask: task3 执行结束...
E/TestTask: task1 执行结束...
E/TestTask: task5 执行结束...
E/TestTask: task6 执行结束...
E/TestTask: task4 执行结束...
E/TestTask: task9 执行结束...
E/TestTask: task7 执行结束...
E/TestTask: task8 执行结束...

这次是10个任务同时一起运行了,并没有先后顺序的,那是因为SynchronousQueue 是一个同步队列,它内部没有容器,也就是根本无法插入任务。按照上面提到的ThreadPoolExecutor执行任务时遵循规则3,如果无法往任务队列插入任务,且未达到线程池规定最大值的话,就会立即启动一个非核心线程来执行任务,所以上面中第10个任务其实是运行在非核心线程中的。

3.4.1 示例3

如果,我们修改一下代码,把MAXIMUM_POOL_SIZE的值换成跟CORE_POOL_SIZE一样是9的话,会怎样?我们运行程序再来看看。

输出结果如下:

E/TestTask: task0 准备执行...
E/TestTask: task1 准备执行...
E/TestTask: task2 准备执行...
E/TestTask: task3 准备执行...
E/TestTask: task4 准备执行...
E/TestTask: task5 准备执行...
E/TestTask: task6 准备执行...
E/TestTask: task7 准备执行...
E/TestTask: task8 准备执行...
E/TestTask: task9 准备执行...
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.zyx.myapplication, PID: 7983
    java.util.concurrent.RejectedExecutionException: Task com.zyx.myapplication.MainActivity$TestTask@6faf040 rejected from java.util.concurrent.ThreadPoolExecutor@f8bfb79[Running, pool size = 9, active threads = 9, queued tasks = 0, completed tasks = 0]
        at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2086)
        at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:848)
        at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1394)
        at com.zyx.myapplication.ThreadPoolManager.execute(ThreadPoolManager.java:54)
        at com.zyx.myapplication.MainActivity$1.onClick(MainActivity.java:47)
        at android.view.View.performClick(View.java:7464)
        at android.view.View.performClickInternal(View.java:7441)
        at android.view.View.access$3600(View.java:823)
        at android.view.View$PerformClick.run(View.java:28381)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7833)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:952)

报java.util.concurrent.RejectedExecutionException异常了,那是因为按照上面提到的ThreadPoolExecutor执行任务时遵循规则4,如果队列已满且线程数量已达到线程池规定的最大值,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法抛出来异常。

3.5 线程池的状态

从ThreadPoolExecutor类的源码可见有一个为AtomicIntege类型的变量ctl,其中高3位用于存储线程池当前的运行状态,低29位代用于存储当前工作线程的数量。

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;

// 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;     // 表示所有任务已经终止,工作线程数量等于0,线程池切换到此状态后将会执行terminated钩子方法
private static final int TERMINATED =  3 << COUNT_BITS;     // 表示terminated钩子方法已经执行完成

ThreadPoolExecutor类还提供了相应的方法来改变线程池状态和查询线程池状态:

public void shutdown()                                     // 进入 SHUTDOWN状态,此时不接收新任务,但会处理完正在运行的 和 在阻塞队列中等待处理的任务。

public List<Runnable> shutdownNow()          // 进入STOP状态,此时不接收新任务,不再处理在阻塞队列中等待的任务,还会尝试中断正在处理中的工作线程。

public boolean isShutdown()                           // 断是否非运行状态,可以是:SHUTDOWN、STOP、TIDYING、TERMINATED

public boolean isTerminating()                        // 断是否正在终止状态,可以是:SHUTDOWN、STOP、TIDYING

public boolean isTerminated()                          // 断是否终止状态:TERMINATED

3.6 任务的提交

3.6.1 execute

execute方法定义在Executor接口中,它是任务提交的核心方法,因为接下来将要介绍的submit和invoke系列方法最终还是会到它这里来执行。来看看它的源码:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
     * Proceed in 3 steps:
     *
     * 1. If fewer than corePoolSize threads are running, try to
     * start a new thread with the given command as its first
     * task.  The call to addWorker atomically checks runState and
     * workerCount, and so prevents false alarms that would add
     * threads when it shouldn't, by returning false.
     *
     * 2. If a task can be successfully queued, then we still need
     * to double-check whether we should have added a thread
     * (because existing ones died since last checking) or that
     * the pool shut down since entry into this method. So we
     * recheck state and if necessary roll back the enqueuing if
     * stopped, or start a new thread if there are none.
     *
     * 3. If we cannot queue task, then we try to add a new
     * thread.  If it fails, we know we are shut down or saturated
     * and so reject the task.
     */
    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);
}

源码中注释比较详细,我们结合代码和注释翻译来理解一下意思。

任务提交分3步进行:

1. 如果当前正在运行的线程数 < corePoolSize(指定的核心线程数),便尝试将任务添加到新的核心线程中去执行,addWorker方法的调用以原子方式检查运行状态和workerCount(线程数量)(上面3.2执行任务规则的第1点)

2. 如果线程池是RUNNING(运行)状态且任务队列允许添加(上面3.2执行任务规则的第2点)

    a). 添加完成后再次检查线程池的状态是否! RUNNING(非运行)状态,如果是则将刚添加的任务回滚移除并拒绝任务。

    b). 添加完成后再次检查核心线程数是为0(corePoolSize被设为0或者设置了allowCoreThreadTimeOut为ture核心线程也被回收的情况),如果是则调用addWorker方法传入null和false给线程池创建一个线程,因为线程池内无线程但队列有任务也不会被执行的。

3. 如果无法将任务添加至队列,则尝试添加新的非核心线程(addWorker传入false)来处理任务(上面2执行任务规则的第3点)。如果仍然失败,则拒绝该任务(上面2执行任务规则的第4点)

3.6.1.1 addWorker创建线程过程

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {

        // 获取线程池当前的状态
        int c = ctl.get();
        int rs = runStateOf(c);
        // 检查线程池的状态和队列是否为空
        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;

        // 尝试增加线程数量WorkerCount++
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();

        // 状态发生过变化,前后不一致,重试
            if (runStateOf(c) != rs)
                continue retry;
        }
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {

   // 传入Runable作为参数创建内部类Worker对象来创建线程
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive())
                        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;
}
private void addWorkerFailed(Worker w) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (w != null)

        // 创建失败,从集合中移除
            workers.remove(w);

// 减回刚加线程数量WorkerCount--
        decrementWorkerCount();
        tryTerminate();
    } finally {
        mainLock.unlock();
    }
}

3.6.1.2 Worker线程对象创建

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    // ……
    // 内部类构造方法,接收Runable和创建线程对象
    Worker(Runnable firstTask) {
        setState(-1);
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }
    public void run() {
        runWorker(this);
    }
    // ……
}

3.6.1.3 runWorker线程的执行

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 ((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);
    }
}

beforeExecute和afterExecute方法在ThreadPoolExecutor类中都是空实现,所以如果有必要可以在创建ThreadPoolExecutor对象时进行对这两个勾子方法的扩展,同时为空实现的方法还有terminated。

protected void beforeExecute(Thread t, Runnable r) { }       // 线程池内任务执行之前
protected void afterExecute(Runnable r, Throwable t) { }     // 线程池内任务执行之后
protected void terminated() { }                              // 线程池进入TERMINATED状态

3.6.2 submit

submit有三个重载方法,它定义在ExecutorService接口中,它主要是可以支持调用者可以通过返回的对象来获取任务执行的结果。其源码如下:

protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}
public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, result);
    execute(ftask);
    return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

可见三个submit方法都是接收参数后通过newTaskFor方法创建一个RunnableFuture(FutureTask)对象,然后将该对象作为参数调用到execute方法去,最后返回FutureTask对象。调用者可以通过FutureTask对象来获取任务执行的结果。

来看看FutureTask是什么:

public class FutureTask<V> implements RunnableFuture<V> {
    // ……
    private volatile int state;
    private static final int NEW          = 0; // 任务创建
    private static final int COMPLETING   = 1; // 任务即将完成
    private static final int NORMAL       = 2; // 任务正常完成
    private static final int EXCEPTIONAL  = 3; // 任务异常完成
    private static final int CANCELLED    = 4; // 任务被取消
    private static final int INTERRUPTING = 5; // 任务正在中断
    private static final int INTERRUPTED  = 6; // 任务已经中断
    // ……
}

FutureTask继承于RunnableFuture,RunnableFuture又继承于Runnable和Future。内部定义了关于任务的7种状态。相关的几个重要方法有:

public boolean cancel(boolean mayInterruptIfRunning)                              // 尝试取消任务,有3种情况:a.若任务已经完成或已经取消,则返回false;b.若任务正在执行中,根据传入的参数决定是否强制终断;c.若任务还在队列中等待,则将任务取消除出队列。

public boolean isCancelled()                                                                       // 判断任务是否被取消,包括:CANCELLED、INTERRUPTING、INTERRUPTED三种状态。

public boolean isDone()                                                                              // 判断任务是否被执行,即非NEW状态。

public V get() throws InterruptedException, ExecutionException               // 获得任务执行的结果,这是一个阻塞等待的方法。

public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException   // 获得任务执行的结果,该方法会待timeout时间,如果没有结果就抛出TimeoutException异常

3.6.3 invokeAny

invokeAny方法也定义在ExecutorService中,它主要是可以支持多任务提交执行时,取得一个率先完成的任务的返回值,从而取消其它尚未完成的任务。其源码如下:

public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
    throws InterruptedException, ExecutionException {
    try {
        return doInvokeAny(tasks, false, 0);
    } catch (TimeoutException cannotHappen) {
        assert false;
        return null;
    }
}

public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException {
    return doInvokeAny(tasks, true, unit.toNanos(timeout));
}
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks, boolean timed, long nanos)
    throws InterruptedException, ExecutionException, TimeoutException {
    // 用于记录未提交的任务数
    int ntasks = tasks.size();
    ArrayList<Future<T>> futures = new ArrayList<>(ntasks);
    // 用于记录已完成的任务
    ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this);

    try {
        ExecutionException ee = null;
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        Iterator<? extends Callable<T>> it = tasks.iterator();

        // 开始第一个任务
        futures.add(ecs.submit(it.next()));
        --ntasks;
        // 用于记录未结束的任务数
        int active = 1;
        // 循环进行任务的提交和判断是否有任务完成
        for (;;) {
            Future<T> f = ecs.poll();
            // 未有任务完成,继续提交新任务
            if (f == null) {
                // 还存在未提交的任务
                if (ntasks > 0) {
                    --ntasks;
                    futures.add(ecs.submit(it.next()));
                    ++active;
                }
                // 全部任务已执行结束,但没有一个任务成功
                else if (active == 0)
                    break;
                //  未有任务成功但超时时间已到,则抛出TimeoutException异常
                else if (timed) {
                    f = ecs.poll(nanos, NANOSECONDS);
                    if (f == null)
                        throw new TimeoutException();
                    nanos = deadline - System.nanoTime();
                }
                //  未有任务成功且没有超时机制,则无限阻塞等待
                else
                    f = ecs.take();
            }
            // 已有任务完成
            if (f != null) {
                --active;
                try {
                    return f.get();
                } catch (ExecutionException eex) {
                    ee = eex;
                } catch (RuntimeException rex) {
                    ee = new ExecutionException(rex);
                }
            }
        }
        if (ee == null)
            ee = new ExecutionException();
        throw ee;
    } finally {
        // 取消还没完成的任务
        cancelAll(futures);
    }
}

两个方法最终调用到doInvokeAny方法去,代码中通过循环进行提交新任务和判断是否存在任务成功执行完成,最终会是等待执行或者成功返回执行完成的任务结果又或者抛出异常来结束的方法,方法最后一定会执行取消所有已提交任务。

3.6.4 invokeAll

invokeAll方法也定义在ExecutorService中,它主要是可以支持多任务提交执行时,等待所有任务都执行完毕后,取得全部任务的结果值。其源码如下:

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    throws InterruptedException {
    if (tasks == null)
        throw new NullPointerException();
    ArrayList<Future<T>> futures = new ArrayList<>(tasks.size());
    try {
        for (Callable<T> t : tasks) {
            RunnableFuture<T> f = newTaskFor(t);
            futures.add(f);
            execute(f);
        }
        for (int i = 0, size = futures.size(); i < size; i++) {
            Future<T> f = futures.get(i);
            if (!f.isDone()) {
                try { f.get(); }
                catch (CancellationException ignore) {}
                catch (ExecutionException ignore) {}
            }
        }
        return futures;
    } catch (Throwable t) {
        cancelAll(futures);
        throw t;
    }
}
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                     long timeout, TimeUnit unit)
    throws InterruptedException {
    if (tasks == null)
        throw new NullPointerException();
    final long nanos = unit.toNanos(timeout);
    final long deadline = System.nanoTime() + nanos;
    ArrayList<Future<T>> futures = new ArrayList<>(tasks.size());
    int j = 0;
    timedOut: try {
        for (Callable<T> t : tasks)
            futures.add(newTaskFor(t));

        final int size = futures.size();

        // Interleave time checks and calls to execute in case
        // executor doesn't have any/much parallelism.
        for (int i = 0; i < size; i++) {
            if (((i == 0) ? nanos : deadline - System.nanoTime()) <= 0L)
                break timedOut;
            execute((Runnable)futures.get(i));
        }

        for (; j < size; j++) {
            Future<T> f = futures.get(j);
            if (!f.isDone()) {
                try { f.get(deadline - System.nanoTime(), NANOSECONDS); }
                catch (CancellationException ignore) {}
                catch (ExecutionException ignore) {}
                catch (TimeoutException timedOut) {
                    break timedOut;
                }
            }
        }
        return futures;
    } catch (Throwable t) {
        cancelAll(futures);
        throw t;
    }
    // Timed out before all the tasks could be completed; cancel remaining
    cancelAll(futures, j);
    return futures;
}

使用invokeAll方法还需要注意一点,在其内部通过循环执行所有任务时,若存在任务异常是不会抛出的,因为其内部进行了异常捕获,这样的目的是为了让所有任务都能执行完成。所以如果需要,外部可以再进行get()方法的调用来触发异常。

4 ScheduledThreadPoolExecutor简述

ScheduledThreadPoolExecutor继承于ThreadPoolExecutor,并实现ScheduledExecutorService接口。上述提到,ScheduledThreadPool和SingleThreadScheduledExecutor两种类型都是通过ScheduledThreadPoolExecutor对象传入不同的参数进行创建的。ScheduledThreadPoolExecutor存在的意义是什么?Timer虽然可以实现任务的延时和周期性执行,但是Timer由一个TimerThread执行,它是单线程的,所以当某一任务存在执行特别耗时或者发生异常时,将会直接影响到后面的任务被延时或者中断,所以在处理多个任务延时或周期性时就需要使用ScheduledThreadPoolExecutor。我们来看看ScheduledThreadPoolExecutor:

4.1 创建和参数

ScheduledThreadPoolExecutor的构造方法源码如下:

public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
    super(corePoolSize, 			//  固定核心线程数
          Integer.MAX_VALUE,	    // 最大线程数是任意大
          DEFAULT_KEEPALIVE_MILLIS, // 10秒超时
          MILLISECONDS,
          new DelayedWorkQueue(), 	// 延时阻塞队列,它是一个内部类,是DelayQueue的变体版
          threadFactory, 
          handler);
}

构造方法中只需要提供:核心线程数、创建线程的工厂和任务失败异常处理的3个参数。其余的:最大线程数为MAX_VALUE、超时时间为10秒、延时阻塞队列为DelayedWorkQueue都是固定的。super指向于ThreadPoolExecutor的构造方法。

4.2 DelayedWorkQueue队列

DelayedWorkQueue是一个无大小限制的二叉堆实现的延时阻塞队列。堆排序原理就是用小顶堆或大顶堆实现最小或最大元素往堆顶移动,这样便能获取堆中延时最短的任务。它跟java.util.concurrent.DelayQueue类似。区别在源码中注释有讲:

static class DelayedWorkQueue extends AbstractQueue<Runnable>
    implements BlockingQueue<Runnable> {
    /*
     * A DelayedWorkQueue is based on a heap-based data structure
     * like those in DelayQueue and PriorityQueue, except that
     * every ScheduledFutureTask also records its index into the
     * heap array. This eliminates the need to find a task upon
     * cancellation, greatly speeding up removal (down from O(n)
     * to O(log n)), and reducing garbage retention that would
     * otherwise occur by waiting for the element to rise to top
     * before clearing. But because the queue may also hold
     * RunnableScheduledFutures that are not ScheduledFutureTasks,
     * we are not guaranteed to have such indices available, in
     * which case we fall back to linear search. (We expect that
     * most tasks will not be decorated, and that the faster cases
     * will be much more common.)
     *
     * All heap operations must record index changes -- mainly
     * within siftUp and siftDown. Upon removal, a task's
     * heapIndex is set to -1. Note that ScheduledFutureTasks can
     * appear at most once in the queue (this need not be true for
     * other kinds of tasks or work queues), so are uniquely
     * identified by heapIndex.
     */
// ……
}

翻译为中文意思大概是:

DelayedWorkQueue 类似 DelayQueue 和 PriorityQueue,是基于基于堆的数据结构,它将每个ScheduledFutureTask的索引记录到堆数组中。

这样就可以在取消任务时不再需要从数组中去查找任务,大大加快了removal的速度(时间复杂度从O(n)降到O(logn)),并且在等待元素上升到顶部前便进行清除从而减少垃圾残留。

但是由于队列中还可能包含非ScheduledFutureTasks的RunnableScheduledFutures的情况,因此我们并不能保证索引可用,在这情况下,将回退到线性搜索。

(我们预测大多数任务不会被包装修饰,所以速度更快的情况会更为常见)。

所有的堆操作都必须记录索引的更改——主要是在siftUp和siftDown两个方法中。

一个任务删除后其headIndex会被置为-1。

注意每个ScheduledFutureTasks在队列中最多出现一次(对于其它类型的任务或者队列不一定只出现一次),所以可能通过heapIndex进行唯一标识。

4.3 任务的提交

4.3.1 schedule

ScheduledThreadPoolExecutor重写了任务提交的execute和submit方法,其源码:

public void execute(Runnable command) {
    schedule(command, 0, NANOSECONDS);
}
public Future<?> submit(Runnable task) {
    return schedule(task, 0, NANOSECONDS);
}
public <T> Future<T> submit(Runnable task, T result) {
    return schedule(Executors.callable(task, result), 0, NANOSECONDS);
}
public <T> Future<T> submit(Callable<T> task) {
    return schedule(task, 0, NANOSECONDS);
}

4个方法最终会调用到schedule方法去,schedule方法用于延时delay后执行一次任务。来看看其源码:

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    RunnableScheduledFuture<Void> t = decorateTask(command, new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit), sequencer.getAndIncrement()));
    delayedExecute(t);
    return t;
}

public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
    if (callable == null || unit == null)
        throw new NullPointerException();
    RunnableScheduledFuture<V> t = decorateTask(callable, new ScheduledFutureTask<V>(callable, triggerTime(delay, unit), sequencer.getAndIncrement()));
    delayedExecute(t);
    return t;
}

4.3.2 scheduleAtFixedRate

scheduleAtFixedRate方法用于延迟initialDelay后执行一次,然后固定每隔period后一直重复执行(固定周期)。来看看其源码:

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    if (period <= 0L)
        throw new IllegalArgumentException();
    ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      unit.toNanos(period),
                                      sequencer.getAndIncrement());
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    delayedExecute(t);
    return t;
}

4.3.3 scheduleWithFixedDelay

scheduleWithFixedDelay方法用于延迟initialDelay后执行一次,然后等上个任务完成后(每个任务时间不定)再每隔delay后重复执行(固定延时)。来看看其源码:

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
    if (command == null || unit == null)
        throw new NullPointerException();
    if (delay <= 0L)
        throw new IllegalArgumentException();
    ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command,
                                      null,
                                      triggerTime(initialDelay, unit),
                                      -unit.toNanos(delay),
                                      sequencer.getAndIncrement());
    RunnableScheduledFuture<Void> t = decorateTask(command, sft);
    sft.outerTask = t;
    delayedExecute(t);
    return t;
}

4.3.4 ScheduledFutureTask

schedule、scheduleAtFixedRate 和 scheduleWithFixedDelay,三类方法的区别其实就是通过不同的参数构造了不一样的ScheduledFutureTask 对象。来看看其源码:

private class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {
    // ……
    public void run() {
        boolean periodic = isPeriodic();
        // 检查当前状态是否能继续执行任务
        if (!canRunInCurrentRunState(periodic))
            cancel(false);
        // 不是周期性执行的任务,直接执行
        else if (!periodic)
            super.run();
        // 周期性任务,执行完一个周期后重置任务状态 
        else if (super.runAndReset()) {
           // 设置下次运行时间
            setNextRunTime();
            reExecutePeriodic(outerTask);
        }
    }
    private void setNextRunTime() {
        long p = period;
        // scheduleAtFixedRate的任务,固定周期,直接将上次时间加上
        if (p > 0)
            time += p;
        // scheduleWithFixedDelay的任务,固定延迟,当前时间加上延迟
        else
            time = triggerTime(-p);
    }
}

4.3.5 delayedExecute方法将任务入队和添加线程

无论是schedule、scheduleAtFixedRate 还是 scheduleWithFixedDelay,三类方法都非常相似,都是通过构造出一个ScheduledFutureTask 对象然后包装成RunnableScheduledFuture对象,然后将该对象作为参数传递到delayedExecute方法去。来看看其源码:

private void delayedExecute(RunnableScheduledFuture<?> task) {
    // 检查状态是否SHUTDOWN
    if (isShutdown())
        reject(task);
    else {
        // 任务入队
        super.getQueue().add(task);
        // 再次检查是否SHUTDOWN && 检查当前状态是否能继续执行任务 && 不能执行则移除任务
        if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) &&  remove(task))
            task.cancel(false);
        else
            ensurePrestart();
    }
}
void ensurePrestart() {
    int wc = workerCountOf(ctl.get());
    if (wc < corePoolSize)
        // 添加核心线程
        addWorker(null, true);
    else if (wc == 0)
        // 添加非核心线程
        addWorker(null, false);
}

5 手动创建线程池的注意

到现在我们已经对Android提供的5种线程池的使用场景、ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 的原理有了大概的认识。现在就来讨论一下如何结合实际开发过程中应该如何来使用线程池。一般地当我们调用Executors类来创建5种线程池时,可能会因为加入了一些代码规范的原因,会弹出”手动创建线程池,效果会更好哦。“的警告。那是因为代码规范里希望我们在使用线程池时最好可以清楚认识创建线时参数的含义和实际需要,如果盲目地使用封装好的方法很容易造成不必要的资源浪费。

那如果不建议使用Executors类,我们参考源码,如果需要使用FixedThreadPool、CachedThreadPool和SingleThreadExecutor类型的线程池时就直接new出ThreadPoolExecutor对象,如果需要使用ScheduledThreadPool和SingleThreadScheduledExecutor类型的线程池时就直接new出ScheduledThreadPoolExecutor对象,然后将此5类线程池进一步封装是不是就可以了?看似没错,虽然大多数情况下能走得通,但如果想将线程池性能发挥到最佳,关键还是需要我们根据实际的业务场景进行实际决定:核心线程数、总线程数、闲置超时时间、线程队列类型和大小、线程池内线程的名称等参数。

5.1 计算密集型

计算密集型任务以CPU计算为主,也就是需要特别多的CPU计算资源,执行任务时CPU处于忙碌状态。对于计算密集型的任务,不要创建过多的线程,过程的线程并不会加快计算速度,反而会消耗更多的内存空间,因为线程有执行栈等内存消耗,而且过多的线程频繁切换线程上下文也会影响线程池的性能。所以最好需要结合CPU核心数来决定线程数,这样才能让优势发挥出来。

一般比较理想的方案是:线程数 = CPU核心数 + 1(可通过Runtime.getRuntime().availableProcessors()获得CPU核心数),因为当计算密集型线程会偶尔因为页缺失故障或其它原因而暂停时,这个+1额外的线程能确保这段时间内的CPU始终周期不会被浪费。

Android提供的FixedThreadPool 类型的线程池如果构造方法传入的参数是CPU核心数+1时,它也算是计算密集型线程池。

5.2 IO密集型

IO密集型任务以IO为主,比如执行文件的读写、数据库的读写、缓存交互、网络请求等阻塞操作,一旦发生IO阻塞,CPU是处于等待状态的,等待过程中操作系统会把CPU时间片分给其他线程,等IO结束后,线程才会继续执行。所以对于IO密集型的线程池最好可以设置更多一些的线程数量,这样就能让等待阻塞的时间内,其它线程可以去做其它的事情来提高任务的吞吐量从而提高并发处理的效率。那么是不是越大越好?当然不是,因为线程上下文切换也是需要代价的,过多的任务执行也会有内存溢出的风险。

一般比较理想的方案是:线程数 = CPU核心数 / (1 - 阻塞系数),一般这个阻塞系统是0.8~0.9之间,当然这也是需要根据实际情况来决定。

Android提供的CachedThreadPool类型的线程池,它也算是IO集型线程池,但是因为它的总线程数是Integer.MAX_VALUE,所以如果直接使用时需要特别注意任务的数量和时间的控制来防止发生内存溢出。

5.3 混合型

在实际开发中,往往会碰到一些任务并非单一是计算密集型或是IO密集型。例如存在着通过网络请求后得到结果,然后再进一步做计算处理等情况。这种类型没有固定的套路,只能是实际问题实际解决了。我们将任务分类型的初衷只是为了将线程池的性能最大化,避免线程数过大和过小情况而已。

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值