Java并发——深入 ThreadPoolExecutor 任务执行原理

Java:ThreadPoolExecutor中的Worker 在文章《从源码看ThreadPoolExecutor的执行步骤》中已经讲过了ThreadPoolExecutor的执行步骤,里面提到了一个内部类Worker,它就是负责具体执行任务的,今天就来说说它。 private final class Worker extends AbstractQueuedSynchronizer implements Runnable { //worker的工作线程,如果创建失败则为null final Thread thread; 阅读详情

1 Executor

它用于执行指定的任务,把任务提交与任务执行分离,程序员不需要关注线程的管理,以及任务的执行。
ExecutorService 接口对 Executor 接口提供更多的扩展,ThreadPoolExecutor 类提供
可以扩展的线程池实现,而 Executors 只是对这些 Executor 提供方便的工厂方法。

1.1 类图

Executor

2 Future

2.1 类图

Future

2.2 FutureTask

类结构:

class FutureTask {
    // 任务运行状态
    /*
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */
    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;
    
    // 底层运行的 callable
    private Callable<V> callable;
    // 任务结果,非 volatile,因为受 state 读写保护
    private Object outcome;
    // 运行 callable 任务的线程
    private volatile Thread runner;
    
    private volatile WaitNode waiters;
    
    //  在 Treiber stack 中记录等待中的线程
    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }
}

两个重要的构造函数:

// 包装 Callable,state = NEW
public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;
}

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

第二个构造器使用了 Executors 工具类的 callable() 方法来把 Runnable 适配成 Callable。典型的
适配器模式。

public static <T> Callable<T> callable(Runnable task, T result) {
    return new RunnableAdapter<T>(task, result);
}

static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

3 ThreadPoolExecutor

3.1 类结构

class ThreadPoolExecutor {
    // 状态控制,包含两个信息:
    // workerCount: 工作线程数
    // runState: 表明线程池运行状态
    //     RUNNING: 可以接受新的 task,且能处理队列中的 task
    //     SHUTDOWN: 不接受新的 task,但能处理队列中的 task
    //     STOP: 不接受新的 task,不处理队列中的 task,并且中断运行中的 task
    //     TIDYING: 所有任务都被终止,workerCount = 0,线程状态为 TIDYING
    //     TERMINATED: terminated() 方法执行完成
    //                  RUNNING
    //       shutdownNow()|    \ shutdown()
    //                    |/    \/
    //                 STOP---- SHUTDOWN
    //            线程池为空|       / 队列和线程池都为空
    //                    |/    \/          \
    //                    TIDYING------------- TERMINATED
    //                            terminated()
    // 初始化:ctl = RUNNING
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // 11101
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // 00011111111111111111111111111111  536870911
    // worker 线程最大数量
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState 存储在高位
    // 111 开头 -536870912
    private static final int RUNNING    = -1 << COUNT_BITS;
    // 000 开头 0
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    // 001 开头 536870912
    private static final int STOP       =  1 << COUNT_BITS;
    // 010 开头 1073741824
    private static final int TIDYING    =  2 << COUNT_BITS;
    // 011 开头 1610612736
    private static final int TERMINATED =  3 << COUNT_BITS;

    // ~CAPACITY = 11100000000000000000000000000000
    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; }

    private static boolean runStateLessThan(int c, int s) {
        return c < s;
    }

    private static boolean runStateAtLeast(int c, int s) {
        return c >= s;
    }

    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }
}

3.2 内部类 Worker

private final class Worker extends AbstractQueuedSynchronizer
        implements Runnable {
    // 执行任务线程
    final Thread thread;
    // 待执行任务
    Runnable firstTask;
    // 每个线程完成任务数
    volatile long completedTasks;
    
    Worker(Runnable firstTask) {
        setState(-1); // 设置AQS的同步状态为-1,禁止中断,直到调用 runWorker
        this.firstTask = firstTask;
        // 使用 ThreadFactory 创建线程
        this.thread = getThreadFactory().newThread(this);
    }
    
    /** Delegates main run loop to outer runWorker  */
    public void run() {
        // 核心
        runWorker(this);
    }
}

3.3 任务执行(execute)

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    // 初始时,ctl = RUNNING = 11100000000000000000000000000000
    int c = ctl.get();
    // wokerCountOf(c) => c & 00011111111111111111111111111111
    // 线程数小于 corePoolSize,创建线程执行 task
    if (workerCountOf(c) < corePoolSize) {
        // 创建线程,并启动线程执行 command
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }

    // 两种情况:
    // ① 线程数大于等于 corePoolSize;
    // ② 线程数小于 corePoolSize,但是 addWorker() 线程池状态不符合条件,或创建线程失败或启动线程失败
    // 线程池处于运行状态才能往队列添加任务
    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);
    }
    // 线程池非 RUNNING 状态
    // 队列已满
    // 此时使用 maximumPoolSize 作为界限判断
    else if (!addWorker(command, false))
        reject(command);
}

/**
 * addWoker() 方法返回 false 几种情况:
 *    1)线程池停止或 shutdown 
 *    2)ThreadFactory 创建线程失败
 * @param core true:使用 corePoolSize 作为界限;false:使用 maximumPoolSize 作为界限
 */
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        
        // ① 线程池状态是 RUNNING,可以创建工作线程,即执行下面的 for 循环
        // ② 线程池状态是 STOP、TIDYING 或 TERMINATED,直接返回 false,即不能创建工作线程执行任务
        // ③ 线程池状态时 SHUTDOWN,此时 firstTask 在此种状态下应该为 null,即它不能接收新的任务
        // 所以如果 firstTask 不为 null,那么可以直接返回 false
        // 如果 firstTask 为 null,此时线程池可以处理队列中任务,所以如果队列为空,那么直接返回 false,否则需要创建工作线程处理队列中的任务
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
        
        // runState 为 RUNNING 或 SHUTDOWN
        for (;;) {
            int wc = workerCountOf(c);
            // woker 线程数超过界限,返回 false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
 
            // CAS 增加 workerCount
            if (compareAndIncrementWorkerCount(c))
                break retry; // 失败
            c = ctl.get();  // Re-read ctl
            // 可能 runState 被其他线程改变,非 RUNNING 或 SHUTDOWN 状态
            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 {
        // 初始化 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) {
                // 实际会调用 Worker 类的 run() 方法
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted) //未能成功创建执行工作线程
            addWorkerFailed(w);  //在启动工作线程失败后,将工作线程从集合中移除
    }
    return workerStarted;
}

从上述代码分析可以看出,有很多书上在分析线程池的执行原理是有问题的。
线程池工作原理
小结上述逻辑:

  • 线程池控制标识是原子类型,即 AtomicInteger
  • execute() 方法只关注线程池核心工作流程,具体执行细节交由其他方法处理,主要是 addWorker() 方法
  • 工作线程小于 corePoolSize 时,创建工作线程以及执行任务统一交给 addWorker() 方法处理
  • addWorker() 方法在创建工作线程执行任务之前,需要判断线程池当前状态是否满足可以创建工作线程。
    • RUNNING 状态可以接收新的任务(能创建工作线程),且能处理队列中的任务
    • SHUTDOWN 状态不能接收新的任务(不能创建工作线程),但能处理队列中的任务
  • 使用 CAS 计数工作线程,即更新线程池控制标识
  • 任务和线程统一由其内部类 Worker 进行封装,Worker 类本身也是 Runnable

整个流程图如下,画的不太好,主要还是为了梳理逻辑,因为整个代码用到了太多 if() 语句的短路思维,当然这些主要是跟线程池的状态有关。
线程执行任务流程图

基于电能质量治理的场景实例:统一电能质量调节器(UPQC)与并网逆变器集成 本文通过Simulink平台,详细演示了统一电能质量调节器(UPQC)与并网逆变器的集成系统建模与仿真过程。主要内容包括:1)系统架构设计,包含光伏并网系统、非线性负载和电压扰动源;2)UPQC主电路设计,包括串联/并联单元拓扑和控制策略实现;3)与并网逆变器的能量交互与协同控制逻辑;4)典型电能质量问题的仿真测试场景。结果表明,该集成系统能有效治理电压暂降(维持负载电压380V±2%)和电流谐波(电网THD<3%),直流母线电压稳定在800V±5V。该系统为应对复杂电能质量问题提供了"电压 阅读详情

相关推荐

抖音X-Medusa风控逆向:unidbg环境重建与魔改AES还原

X-Medusa并非传统加密算法,而是深度耦合运行时环境的动态混淆系统,其核心是硬件指纹感知、线程上下文绑定与非标AES变异。理解其原理需跳出‘找密钥’思维,转向对CPU型号、系统属性、微秒级时间及内存布局的综合建模。技术价值在于将不可控的客户端风控,转化为可复现、可配置、可运维的服务化能力;典型应用场景包括合规数据采集、API协议适配与移动安全研究。本文聚焦Android native层逆向实践,基于unidbg实现高保真环境模拟,并完整还原S-Box动态生成、MixColumns矩阵变异与轮密钥XOR扰

weixin_33783283的博客 689

java线程池ThreadPoolExecutor类使用详解

在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThrea...

dianyuzhi7625的博客 1423

聚类模型-层次聚类

聚类模型 1、层次聚类 2、原型聚类-K-means 3、模型聚类-GMM 4、EM算法-LDA主题模型 5、密度聚类-DBSCAN 6、图聚类-谱聚类 一、层次聚类 一、聚类理论 ​ 一般来说,聚类是在训练样本的标签信息不知的情况下,学习样本内在的性质和规律,将有限的集合划分成ccc类。根据“方以类聚,物以群分”的思想,类内对象尽可能的相似,...

myazi 6504

newFixedThreadPool使用范例

newFixedThreadPool使用范例: Java代码   import java.io.IOException;   import java.util.concurrent.ExecutorService;   import java.util.concurrent.Executors;     public class Test {         public

6219

二、基于C语言的JPEG解码代码详解

// JPEG解码 // 输入JPEG后输出Bitmap #include <stdio.h> #include <stdlib.h> #pragma warning(disable : 4996) unsigned int BuffIndex; // JPEG数据的位置 unsigned int BuffSize; // JPEG数据的大小 unsigned int BuffX; // 图像的横向尺寸 unsigned int BuffY; //

m0_37633745的博客 4458

基于人脸识别的智能门禁系统设计

在当前的技术背景下,本文精心打造了一款前沿的视频识别门禁控制系统。在进行调试的时候使用的软件是Keil,编写完代码,生成文件后用下载器下载到单片机上,就能够进行程序的调试了,本次设计实现了预先设定的目标,达到了预期的效果。本次设计的人脸识别的门禁控制系统其硬件部分是以STM32F103C8T6为核心的单片机,采用0V2640摄像头对人员的人脸识别,采用矩阵按键对系统的操作以及密码的输入,使用蜂鸣器来作为报警模块,开门装置是电子锁控制,显示设备采用OLED液晶显示,无线模块在一番思索后选择了蓝牙模块传输。

2401_86147581的博客 1297

Java并发编程:Java线程池核心ThreadPoolExecutor的使用和原理分析

引出线程池 线程是并发编程的基础,前面的文章里,我们的实例基本都是基于线程开发作为实例,并且都是使用的时候就创建一个线程。这种方式比较简单,但是存在一个问题,那就是线程的数量问题。 假设有一个系统比较复杂,需要的线程数很多,如果都是采用这种方式来创建线程的话,那么就会极大的消耗系统资源。首先是因为线程本身的创建和销毁需要时间,如果每个小任务都创...

Java爬坑之路 318

保姆级教程:用Kalibr搞定Intel D435i三目(RGB+双目)相机联合标定,附完整ROSbag录制避坑指南

本文详细介绍了如何使用Kalibr工具对Intel D435i三目相机(RGB+双目)进行高精度联合标定,包括硬件准备、ROSbag录制技巧、Kalibr标定实战及结果验证。特别针对实际工程中的常见问题提供了解决方案,帮助开发者提升SLAM和三维重建等应用的精度。

weixin_42524762的博客 405

Java线程池ThreadPoolExcutor任务分配、执行顺序

Java线程池ThreadPoolExcutor任务分配、执行顺序

qq_25883735的博客 1370

STM32入门教程:图像处理

然后,我们使用 STM32 的 DSP 模块中的 arm_mean_u8 函数计算原始图像的平均值,并将结果存储在 meanValue 变量中。然后,我们需要编写代码配置 STM32 的外部中断输入引脚,并在中断处理函数中读取 OV7670 摄像头模块输出的图像数据。然后,我们需要编写代码配置 STM32 的 USB OTG 接口,并在回调函数中读取 USB 摄像头输出的图像数据。一、图像采集 图像采集是图像处理的第一步,我们需要将外部图像源的数据导入到 STM32 中进行处理。

2401_85625565的博客 1616

ThreadPoolExecutor 创建线程池

简单事例: ThreadFactory nameFactory = new ThreadFactoryBuilder().setNameFormat("send-pool-%d").build(); ExecutorService executor = new ThreadPoolExecutor(3, 5, 0L, TimeUnit.MILLISECONDS, new ...

zhuzixi-的博客 613

Qt界面美化-飞扬青云自绘控件插件的使用-避免采坑

1、先上一个集成到QtCreater中的效果图: 2、飞扬青云自绘控件插件下载地址:https://gitcode.net/mirrors/feiyangqingyun/qucsdk?utm_source=csdn_github_accelerator 3、集成插件到QtCreater中的方法: (1)、查看自己使用的QtCreater的版本信息,我的:Qt 5.15.2 MSVC2019 64位 (2)插件下载下来后工程预览: 我选择msvc版本的SDK,打开找到匹配的SDK:...

小米的修行之路 2793

Java编程的逻辑 (77) - 异步任务执行服务

​本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http://item.jd.com/12299018.html Java并发包提供了一套框架,大大简化了执行异步任务所需的开发,本节我们就来初步探讨这套框架。 在之前的介绍中,线程Thread既表示...

156

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

深入解析Java线程池:ThreadPoolExecutor设计精髓

线程池设计通过循环复用线程和阻塞队列解决核心矛盾。其核心机制是将线程run()方法封装在循环中,由线程池策略控制生命周期,实现线程复用。当无任务时,线程通过阻塞队列(生产者-消费者模型)进入等待状态避免CPU空耗。JUC线程池在此基础上扩展出精细配置:通过core/max线程数平衡资源与响应;提供多种队列和拒绝策略应对任务特性;支持线程工厂定制和生命周期管理。

lifallen的博客 1927

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Java线程池之ThreadPoolExecutor源码解析

点击此处去Gitee上Clone源码下来在IDE上看效果更佳 Executor package java.util.concurrent; /** * 执行提交的可运行任务的对象。 */ public interface Executor { /** * 在将来的某个时间执行给定命令。 * 根据{@code Executor}实现的判断,命令可以在新线程,池线程或调用线程中执行。 * * @param command the runnable

LittleWear的博客 258

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

并发编程 — ThreadPoolExecutor 线程池实现原理

一、线程池的概念 线程池(Thread Pool)是一种基于池化思想管理线程的工具。类似于我们连接数据库的连接池。 线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题: 创建一个线程,却需要调用操作系统内核的 API,然后操作系统要为线程分配一系列的资源,创建成本很高 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。 系统无法合

small_love的专栏 2081

Java并发编程之线程池原理剖析

在前面的学习中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。 那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务? 在Java中可以通过线程池来达到这样的效果。今天我们就来...

Geffin的博客 326

并发编程经历:线程池的使用

线程池的使用 使用线程池管理线程可以最大程度的利用线程,节省资源消耗,它通过利用已有的线程多次循环执行多个任务从而提高系统的处理能力。 我们可以通过java.util.concurrent.ThreadPoolExecutor类来创建线程池,一个任务通过execute(Runnable)方法被添加到线程池,任务就是一个Runnable类型的对象,任务的执行方法就是Runnable类型对象的...

yjclsx的博客 42万+

ThreadPoolExecutor源码分析二

接上文,这里继续分析源码 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 bit...

dlmk73708的博客 94

Java并发编程第十篇(ThreadPoolExecutor线程池组件分析)

在大部分JVM上, 用户线程与操作系统内核线程是1:1的关系, 也就是说每次创建回收线程都要进行内核调用, 开销较大。那么有了线程池, 就可以重复使用线程资源, 大幅降低创建和回收的频率。此外, 也能一定程度上避免有人在写BUG时, 大量创建线程导致资源耗尽。 * **便于管理** 线程池可以帮你维护线程ID、线程状态等信息, 也可以帮你统计任务执行状态等信息。 理解了线程池的意义, 那么本文的主角便是JUC提供的线程池组件: ThreadPoolExecutor. 请注意, 有人会将

Du_XiaoNan的博客 1350

Java并发包的学习(二)---Executor和ExecutorService的底层实现原理

一.实现原理 ExecutorService的主要实现类是ThreadPoolExecutor,它是基于线程实现的,继承于AbstractExecutorService。AbstractExecutorService是一个抽象类,实现了ExecutorService的部分方法。 1.AbstractExecutorService AbstractExecutorService提供...

无名小辈 945

《Java并发编程实战深度解析线程池原理与ThreadPoolExecutor核心优化技巧》

记住:最好的线程池配置,永远是下一次调优后的版本!| `DiscardPolicy` | 安静丢弃任务 | 容忍任务丢失的场景 || `corePoolSize` | 核心线程池大小 | 1 || `DiscardOldestPolicy` | 丢弃队列中最老的任务 | 不允许堆积的任务流 || `CallerRunsPolicy` | 由提交者线程执行任务 | 容忍延迟的批处理任务 |

LKXAiKWN的博客 923

ThreadPoolExecutor超详细源码解读

ExecutorService ExecutorService是一个接口,此接口继承自Executor接口,Executor接口只有一个方法的定义就是execute接收了一个参数Runable,Runable接口中只有个方法run,结构如下。 Executor---(依赖)--->Runable ↑(继承) ExecutorService ↑(实现) Ab...

weixin_33984032的博客 219

Java并发——线程池ThreadPoolExecutor类源码解析

线程池作为减少线程创建关闭开销的常用手段,一直被使用着,ThreadPoolExecutor类则是Java中内置的线程池中的一种

溯夜,逆流而上 798

Java并发编程:线程池的使用

Java并发编程:线程池的使用   在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:   如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。   那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任...

weixin_30381793的博客 134
上一篇: Java并发——深入 ReadWriteLock 原理
下一篇: Spring Boot 集成 prometheus,Grafana 监控
迷茫的老人
博客等级 码龄15年 21粉丝 26原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值