Java线程池ThreadPoolExecutor使用和分析(一)

【亲测可用!】阿里达摩院开源实时数字人对话VideoChat本地部署教程(8G显存可跑) 本文主要记录在部署阿里达摩院开源实时数字人对话VideoChat时遇到的问题,希望对各位技术开发或爱好者有所帮助! 阅读详情

相关文章目录:

    Java线程池ThreadPoolExecutor使用和分析(一)

    Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理

    Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理

    线程池是可以控制线程创建、释放,并通过某种策略尝试复用线程去执行任务的一种管理框架,从而实现线程资源与任务之间的一种平衡。

    以下分析基于 JDK1.7

    以下是本文的目录大纲:

    一、线程池架构

        1、Executor接口

        2、ExecutorService接口

        3、ScheduledExecutorService接口

    二、ThreadPoolExecutor

        1、ThreadPoolExecutor构造参数

        2、ThreadPoolExecutor线程池执行流程

    三、Executors静态工厂创建几种常用线程池

一、线程池架构

 概括一下:

Executor是最基础的执行接口;

ExecutorService接口继承了Executor,在其上做了一些shutdown()、submit()的扩展,可以说是真正的线程池接口;

AbstractExecutorService抽象类实现了ExecutorService接口中的大部分方法;

TheadPoolExecutor继承了AbstractExecutorService,是线程池的具体实现;

ScheduledExecutorService接口继承了ExecutorService接口,提供了带"周期执行"功能ExecutorService;

ScheduledThreadPoolExecutor既继承了TheadPoolExecutor线程池,也实现了ScheduledExecutorService接口,是带"周期执行"功能的线程池;

Executors是线程池的静态工厂,其提供了快捷创建线程池的静态方法。

1、Executor接口

“执行者”接口,只提供了一个方法:

void execute(Runnable command);


可以用来执行已经提交的Runnable任务对象,这个接口提供了一种将“任务提交”与“任务执行”解耦的方法。

2、ExecutorService接口

“执行者服务”接口,可以说是真正的线程池接口,在Executor接口的基础上做了一些扩展,主要是

(A) 管理任务如何终止的 shutdown相关方法

/**
 * 启动一次有序的关闭,之前提交的任务执行,但不接受新任务
 * 这个方法不会等待之前提交的任务执行完毕
 */
void shutdown();
 
/**
 * 试图停止所有正在执行的任务,暂停处理正在等待的任务,返回一个等待执行的任务列表
 * 这个方法不会等待正在执行的任务终止
 */
List<Runnable> shutdownNow();
 
/**
 * 如果已经被shutdown,返回true
 */
boolean isShutdown();
 
/**
 * 如果所有任务都已经被终止,返回true
 * 是否为终止状态
 */
boolean isTerminated();
 
/**
 * 在一个shutdown请求后,阻塞的等待所有任务执行完毕
 * 或者到达超时时间,或者当前线程被中断
 */
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;


 

(B) 可以生成用于追踪一个或多个异步任务执行结果的Future对象的 submit()相关方法

/**
 * 提交一个可执行的任务,返回一个Future代表这个任务
 * 等到任务成功执行,Future#get()方法会返回null
 */
Future<?> submit(Runnable task);
 
/**
 * 提交一个可以执行的任务,返回一个Future代表这个任务
 * 等到任务执行结束,Future#get()方法会返回这个给定的result
 */
<T> Future<T> submit(Runnable task, T result);
 
/**
 * 提交一个有返回值的任务,并返回一个Future代表等待的任务执行的结果
 * 等到任务成功执行,Future#get()方法会返回任务执行的结果
 */
<T> Future<T> submit(Callable<T> task);

3、ScheduledExecutorService接口

/**
 * 在给定延时后,创建并执行一个一次性的Runnable任务
 * 任务执行完毕后,ScheduledFuture#get()方法会返回null
 */
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
 
/**
 * 在给定延时后,创建并执行一个ScheduledFutureTask
 * ScheduledFuture 可以获取结果或取消任务
 */
public <V> ScheduledFuture<V> schedule(Callable<V> callable, ong delay, TimeUnit unit);
 
/**
 * 创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期
 * 也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推
 * 如果执行任务发生异常,随后的任务将被禁止,否则任务只会在被取消或者Executor被终止后停止
 * 如果任何执行的任务超过了周期,随后的执行会延时,不会并发执行
 */
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
 
/**
 * 创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟
 * 如果执行任务发生异常,随后的任务将被禁止,否则任务只会在被取消或者Executor被终止后停止
 */
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

二、ThreadPoolExecutor
1、ThreadPoolExecutor构造参数

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


corePoolSize

线程池中的核心线程数,当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;

如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;

如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

maximumPoolSize

线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize

keepAliveTime

线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间。默认情况下,该参数只在线程数大于corePoolSize时才有用

workQueue

workQueue必须是BlockingQueue阻塞队列。当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能

几种排队的策略:

(1)不排队,直接提交
将任务直接交给线程处理而不保持它们,可使用SynchronousQueue
如果不存在可用于立即运行任务的线程(即线程池中的线程都在工作),则试图把任务加入缓冲队列将会失败,因此会构造一个新的线程来处理新添加的任务,并将其加入到线程池中(corePoolSize-->maximumPoolSize扩容)
Executors.newCachedThreadPool()采用的便是这种策略

(2)无界队列

可以使用LinkedBlockingQueue(基于链表的有界队列,FIFO),理论上是该队列可以对无限多的任务排队

将导致在所有corePoolSize线程都工作的情况下将新任务加入到队列中。这样,创建的线程就不会超过corePoolSize,也因此,maximumPoolSize的值也就无效了

(3)有界队列

可以使用ArrayBlockingQueue(基于数组结构的有界队列,FIFO),并指定队列的最大长度

使用有界队列可以防止资源耗尽,但也会造成超过队列大小和maximumPoolSize后,提交的任务被拒绝的问题,比较难调整和控制。

threadFactory

创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名

/**
 * The default thread factory
 */
static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
 
    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }
 
    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}


Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”

RejectedExecutionHandler(饱和策略)

线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:

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

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

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

(4)DiscardPolicy:直接丢弃任务;

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

2、ThreadPoolExecutor线程池执行流程

根据ThreadPoolExecutor源码前面大段的注释,我们可以看出,当试图通过execute方法将一个Runnable任务添加到线程池中时,按照如下顺序来处理:

(1)如果线程池中的线程数量少于corePoolSize,就创建新的线程来执行新添加的任务;

(2)如果线程池中的线程数量大于等于corePoolSize,但队列workQueue未满,则将新添加的任务放到workQueue中,按照FIFO的原则依次等待执行(线程池中有线程空闲出来后依次将队列中的任务交付给空闲的线程执行);

(3)如果线程池中的线程数量大于等于corePoolSize,且队列workQueue已满,但线程池中的线程数量小于maximumPoolSize,则会创建新的线程来处理被添加的任务;

(4)如果线程池中的线程数量等于了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理

总结,当有新的任务要处理时,先看线程池中的线程数量是否大于corePoolSize,再看缓冲队列workQueue是否满,最后看线程池中的线程数量是否大于maximumPoolSize

另外,当线程池中的线程数量大于corePoolSize时,如果里面有线程的空闲时间超过了keepAliveTime,就将其移除线程池

最后,通过下面的图来看看线程池中的任务调度策略:

图1

 

图2

 

说明:

在图1中,线程池中有N个任务。"任务1", "任务2", "任务3"这3个任务在执行,而"任务3"到"任务N"在阻塞队列中等待。正在执行的任务,在workers集合中,workers集合包含3个Worker,每一个Worker对应一个Thread线程,Thread线程每次处理一个任务。

当workers集合中处理完某一个任务之后,会从阻塞队列中取出一个任务来继续执行,如图2所示。图2表示"任务1"处理完毕之后,线程池将"任务4"从阻塞队列中取出,放到workers中进行处理。

三、Executors静态工厂创建几种常用线程池
Exectors工厂类提供了线程池的初始化接口,主要有如下几种:

newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
 
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}


创建一个指定工作线程数的线程池,其中参数 corePoolSize 和 maximumPoolSize 相等,阻塞队列基于LinkedBlockingQueue

它是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
 
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}


初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行,内部使用LinkedBlockingQueue作为阻塞队列

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
 
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}


创建一个可缓存工作线程的线程池,默认存活时间60秒,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;

在没有任务执行时,当线程的空闲时间超过keepAliveTime,则工作线程将会终止,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销

newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
 
public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}


初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据

注意:

ScheduledExecutorService#scheduleAtFixedRate() 指的是“以固定的频率”执行,period(周期)指的是两次成功执行之间的时间

比如,scheduleAtFixedRate(command, 5, 2, second),第一次开始执行是5s后,假如执行耗时1s,那么下次开始执行是7s后,再下次开始执行是9s后

而ScheduledExecutorService#scheduleWithFixedDelay()  指的是“以固定的延时”执行,delay(延时)指的是一次执行终止和下一次执行开始之间的延迟

还是上例,scheduleWithFixedDelay(command, 5, 2, second),第一次开始执行是5s后,假如执行耗时1s,执行完成时间是6s后,那么下次开始执行是8s后,再下次开始执行是11s后

AI摄影师:自动构图与修图算法设计 AI摄影师:自动构图与修图算法设计​ ,人工智能,计算机视觉,大模型,AI,AI 摄影师的核心能力体现在两方面:自动构图(通过算法优化画面元素布局)与智能修图(自动调整光影、色彩、细节)。例如,手机摄影中的 “AI 构图建议” 功能可实时框选最佳拍摄区域,修图软件中的 “一键增强” 能在 0.1 秒内完成专业级调色,这些都依赖于计算机视觉与深度学习的深度融合。 阅读详情

相关推荐

基于单目视觉的四旋翼定点降落——如何通过mavros控制pixhawk(二)

    一直拖了这么久没有更新,怪我太懒啦~~~ 今天要特别感谢舒仔仔同学,我的好队友帮我写了这篇博文。这篇文章主要讲了如何通过mavros来控制pixhawk。硬件逻辑是这样子,我们先搞一个odroid(土豪上TX2),再用usb线将odroid与pixhawk连接。所以,odroid发送控制指令来控制pixhawk飞行。pixhawk与外界的通信协议是mavlink。ROS下面有个包叫mavr...

zhengyuxin0507的博客 1万+

线程池详解

定义 线程池(ThreadPool)是一种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在一个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。常见的运用池化思想的有:内存池、数据库连接池。使用线程池的优点如下: 优点 1.减少频繁创建和销毁线程的性能开销 2.重复利用线程 避免对每个任务都创建线程 可以提高响应速度 3.合理设置线程池的大小 可以避免因为线程池过大影响性能 线程池种类 1.Executors.newFixedT

qq_41956309的博客 1199

Win10下ISE14.7 iMPACT闪退?三步搞定DLL替换(附详细路径)

本文详细介绍了在Windows 10系统下解决Xilinx ISE14.7 iMPACT工具闪退问题的三步DLL替换方案。通过替换关键DLL文件,有效解决了FPGA开发中的兼容性问题,帮助开发者快速恢复工作流程。

weixin_29290259的博客 577

java ThreadPoolExecutor 详解

构造方法如下: public ThreadPoolExecutor( int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler ) corePoolSize 核心线程数,当提交一个任务的时候,线程池创建一个新的线程执行任务,直到

半夏_2021的博客 1853

《揭秘 BME688人工智能压力湿度温度气体传感器:气体检测的多面能手》:此文为AI自动生成

BME688 是博世推出的一款具有人工智能和集成高线性度高精度压力、湿度、温度传感器的气体传感器,专为移动和联网应用开发,尺寸紧凑且低功耗。BME688 作为一款先进的传感器,在多个领域展现出卓越的性能。它采用了坚固而紧凑的 3.0 x 3.0 x 0.9 mm³ 封装,这种小巧的尺寸使其能够轻松应用于对空间要求严格的移动设备和联网应用中。同时,低功耗的特性也使得它在能源有限的环境下能够长时间稳定运行。

zheng_ruiguo的专栏 1921

【Scrapy】Scrapy教程4——命令行工具

Scrapy支持使用模块设置自己的项目命令。如何实现可参考的实践方法。然后在项目的settings文件中配置来添加自定义命令。以上就是对Scrapy命令行工具的说明,有不明确的地方,可参考官方文档欢迎大家在各大平台关注我,我会不定期发布技术相关文章和各种小工具,并且欢迎共同技术探讨!

jayhgq的博客 1249

ThreadPoolExecutor中运行线程名称的修改

项目中使用到了ThreadPoolExecutor,这个是挺好的东西,线程池的实现。但有一点不太爽的是,用Jprofiler调试由它创建的线程的时候,看到的都是pool-1-thread-1\2\3\4之类的。这是它自身的默认规则产生的,pool代表这是池,-1-代表是第几个ThreadPoolExecutor产生的,-thread-1是递增的。 所以每次我都要点 Thread Dumps 去查

蒲公英的约定 3万+

逆向实战:如何绕过iTunes 12.6.5.3的反调试机制(附X64dbg插件配置)

本文深入剖析了iTunes 12.6.5.3的反调试机制与登录协议。通过X64dbg动态调试与IDA静态分析,详细演示了如何绕过其反调试检测,并定位到核心的登录协议加密函数,揭示了其请求签名(X-Apple-ActionSignature)的生成逻辑与依赖关系,为客户端安全研究与协议分析提供了实战思路。

weixin_29202213的博客 739

ThreadPoolExecutor指定线程名称前缀

ThreadPoolExecutor使用的时候指定线程前缀,便于问题排查

s07aser123的专栏 2158

工程级 RK3588 YOLOv5 Android 架构拆解:NPU 量化优化 + 多线程推理 + 跟踪算法源码

本文将带你完成一个完整的AI视觉项目:在搭载RK3588芯片的Android开发板上运行实时目标检测与跟踪应用。实战目标:成功运行官方YOLOV5摄像头实时检测案例技术深度:深入理解RKNN开发框架和源码实现核心技能:掌握NPU推理、多线程并发、目标跟踪等关键技术。

LXY_LOVE36的博客 128

【AIGC】Qwen3-Embedding:Embedding与Rerank模型新标杆

Qwen3-Embedding:文本嵌入与重排序模型的技术解析与应用实践

一个内容严谨、方向前沿、极具实践价值的AI大模型技术分享站 3085

定时线程池之ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor是jdk提供的基础线程池之一。具有定时执行任务的功能。jgk中另外两种具有定时功能的线程池SingleThreadScheduledExecutor与ScheduledThreadPool实质上都是由ScheduledThreadPoolExecutor来实现的。其继承自基础线程池ThreadPoolExecutor。ScheduledThre...

积 4700

Spring Boot入门(03): 从零开始学习配置文件:掌握YAML和Properties,轻松应对多环境配置管理

在Spring Boot开发中,配置文件是非常重要的。本文将带领你从零开始学习配置文件,掌握YAML和Properties的语法,轻松应对多环境配置管理。无论是开发、测试还是生产环境,你都可以轻松切换不同的配置文件,并且能够灵活修改配置信息。

**My Coding Family** 4万+

java线程池ThreadPoolExecutor使用和分析(一)

线程池是可以控制线程创建、释放,并通过某种策略尝试复用线程去执行任务的一种管理框架,从而实现线程资源与任务之间的一种平衡。     以下分析基于 JDK1.7       以下是本文的目录大纲:     一、线程池架构         1、Executor接口         2、ExecutorService接口         3、ScheduledE

insis_mo的博客 359

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Java并发包源码学习之线程池(一)ThreadPoolExecutor源码分析

Java中使用线程池技术一般都是使用Executors这个工厂类,它提供了非常简单方法来创建各种类型的线程池: public static ExecutorService newFixedThreadPool(int nThreads) public static ExecutorS...

chuxi4620的博客 183

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

java threadpoolexecutor api_Java多线程之线程池(ThreadPoolExecutor)实现原理分析(一)...

在上一篇文章Java中实现多线程的3种方法介绍和比较中,我们讲解了Java中实现多线程的3种方法。使用多线程,就必须要考虑使用线程池,今天我们来聊聊线程池的那些事。注:源码都是基于JDK1.8一、为什么要使用线程池?如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,...

weixin_42356475的博客 240

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

上一篇: Spring-TX-Propagation-事务传播行为
limeOracle
博客等级 码龄10年 11粉丝 566原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值