Future以及FutureTask

Future、Callable、FutureTask、Runnable它们的关系你弄懂了吗 引言 看到标题一下子好几个名称,很可能你都知道、都听过、看过,但是你真是理清楚了它们的关系了吗? 在这个知识泛滥、技术焦虑的时刻,人人嘴里喷着高并发、大数据、分布式,很多估计对这个一头雾水,无论在开发还是面试过程中,一知半解还不如不知。 Runnable/Thread 通常情况下的耗时操作会交给多线程来处理,Java中开启一个新线程很容易,继承自Thread或实现Runnable接口。下面是... 阅读详情

Future介绍

在学习Future之前,需要先了解传统模式与future模式的区别,我觉得这个很重要。但是确实又不好说明,另起一篇文章。

Future模式介绍_Mr_moving-优快云博客_future模式

future模式是一种并发设计模式,Future接口是JDK对该模式提供的一种实现,两者并无必然关系。


1. Future 表示异步的结果计算,Future 接口主要定义了5个方法: 

1) boolean cancel(boolean mayInterruptIfRunning):试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。此方法返回后,对 isDone() 的后续调用将始终返回 true。如果此方法返回 true,则对 isCancelled() 的后续调用将始终返回 true。 

2)boolean isCancelled():如果在任务正常完成前将其取消,则返回 true。 

3)boolean isDone():如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。 

4)V get() throws InterruptedException,ExecutionException:如有必要,等待计算完成,然后获取其结果。 

5)V get(long timeout,TimeUnit unit) throws InterruptedException,ExecutionException,TimeoutException:尝试获取其结果,如有必要,最多等待为使计算完成所给定的时间timeout,如果超时仍未完成计算,抛出超时异常。

InterruptedException 线程中断异常

ExecutionException 任务执行异常,比如中途被取消

TimeoutException 超时异常


2. Future的类图结构

RunnableFuture:这个接口同时继承Future接口和Runnable接口,在成功执行run()方法后,可以通过Future访问执行结果。这个接口的实现类是FutureTask,一个可取消的异步任务,这个类提供了Future的基本实现,后面我们的demo也是用这个类实现,它实现了启动和取消一个任务,查询这个任务是否已完成,获取任务结果。任务的结果只能在任务已经完成的情况下获取。如果任务没有完成,get方法会阻塞,一旦任务完成,这个任务将不能被重启和取消,除非调用runAndReset方法。

FutureTask:能用来包装一个Callable或Runnable对象,因为它实现了RunnableFuture接口,而且它能被传递到Executor进行执行。

SchedualFuture:这个接口表示一个可以延时任务的结果。

其他如 CompleteFuture,ForkJoinTask 了解的不多,用的也比较少,暂时就不做记录。


3. FutureTask 源码分析

字段分析

    private volatile int state;

    /** 任务目标 */
    private Callable<V> callable;
    /**  任务结果,非volatile,受state读/写保护 */
    private Object outcome; 
    /** 底层执行callable任务的线程,在run()方法中使用CAS操作赋值 */
    private volatile Thread runner;
    /** 阻塞在get方法上的线程节点,传动栈数据结构,后进先出 */
    private volatile WaitNode waiters;

一、state 任务的状态值

使用volatile修饰,FutureTask中使用CAS操作更新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;

0==NEW==>初始创建时的状态

1==COMPLETING==>当任务执行完毕,FutureTask会将执行结果设置给outcome属性,在设置之前会将FutureTask的状态修改为COMPLETING。

2==NORAML==>当任务执行完毕,FutureTask会将执行结果设置给outcome属性,在设置之后会将FutureTask的状态修改为NORMAL。

3==EXCEPTIONAL==>当任务在执行的过程中抛了异常,FutureTask会将异常信息设置给outcome属性,在设置之前会将FutureTask的状态修改为COMPLETING,在设置之后将状态修改为EXCEPTIONAL。

4==CANCELLED==>当外部想要取消任务,而又不允许当任务正在执行的时候被取消时会将FutureTask的状态修改为CANCELLED。

5==INTERRUPTING==>当外部想要取消任务,同时允许当任务正在执行的时候被取消时,会先将FutureTask的状态设置为INTERRUPTING,然后设置执行任务的线程的中断标记位。

6==INTERRUPTED==>当外部想要取消任务,同时允许当任务正在执行的时候被取消时,会先将FutureTask的状态设置为INTERRUPTING,然后设置执行任务的线程的中断标记位,最后将Future的状态设置为INTERRUPTED。

其中0为初始状态,1和5为中间状态,其他为终止状态,在FutureTask中,state只会被set(), setException(),cancel()修改为终止状态。

综上,FutureTask的状态转换流转可能为:
1. 任务执行正常
NEW—>COMPLETING—>NORMAL
2. 任务执行异常
NEW—>COMPLETING—>EXCEPTIONAL
3. 任务未得到执行直接取消,或者任务正在执行过程中被取消,但是不允许中断cancel(false)
NEW—>CANCELLED
4. 任务正在执行过程中被取消,允许执行中进行中断cancel(true)
NEW—>INTERRUPTING—>INTERRUPTED

二、runner 正在执行此任务的线程

一个任务同时只能被一个线程执行,在run()方法中通过cas进行更新

三、outcome 任务运行的结果

任务结果的赋值

    public void run() {
        // 仅当任务状态为NEW,且无线程用于运行此任务时,更新runner并开始执行任务。
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    // 任务完成,但执行中出现异常,将异常信息传入赋值给outcome
                    setException(ex);
                }
                if (ran)
                    // 任务正常完成,将结果传入赋值给outcome
                    set(result);
            }
        } finally {
            /**
             * state被设置前必须保证runner非空,以阻止run()被并发调用。
             * 进入finally块,则state已经被设置,任务执行完毕,此时可以安全的将runner置为null  
             */ 
            runner = null;
            // runner置为null后,必须重新读取state以防止任务在执行中被取消,
            int s = state;
            // 有中断发生,以进行相应的逻辑操作 
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

    protected void setException(Throwable t) {
        // 更新任务状态,此时为中间态COMPLETING
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            // 更新最终状态为EXCEPTIONAL
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            // 任务完成后,假设有线程阻塞在get方法上,调用此方法进行唤醒,放在它处分析
            finishCompletion();
        }
    }

    protected void set(V v) {
        // 更新任务状态,此时为中间态COMPLETING
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            // 更新最终状态为NORMAL
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

   

任务结果通过get()方法获取

 public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            /**
             * 阻塞等待任务完成返回
             * 任务完成包含多种情形:
             * 1. 正常完成,状态值为2NORMAL
             * 2. 执行过程中出现异常,状态值为3EXCEPTIONAL
             * 3. 任务取消,状态值为4CANCELLED或者6INTERRUPTED
             * 假设允许中断取消,线程中断,awaitDone也会直接抛出中断异常InterruptedException
             **/
            s = awaitDone(false, 0L);
        // 任务完成返回,根据状态值对结果outcome进行处理并返回
        return report(s);
    }

    private V report(int s) throws ExecutionException {
        Object x = outcome;
        // 正常完成
        if (s == NORMAL)
            return (V)x;
        // 取消
        if (s >= CANCELLED)
            throw new CancellationException();
        // 通过上面的分析,这里的状态值应该就是为3EXCEPTIONAL了,outcome为异常对象,抛出
        throw new ExecutionException((Throwable)x);
    }

通过上面的分析,也了解到了状态值state的一些变化流程。其中有个问题,为什么要多此一举,先把状态改为COMPLETING然后又改为COMPLETED,不能直接改为COMPLETED吗?

参考文章FutureTask的使用方法及实现原理 - 掘金

四、waiters 等待获取结果的线程

waiters 维护了阻塞在get方法上的线程节点,使用传动栈数据结构,后进先出。Treiber栈是一个无锁数据结构,FutureTask中的waiters变量指向这个栈的栈顶。入栈操作只通过一步CAS操作实现,即修改栈顶指针waiters;出栈和在栈的中间执行删除操作通过特定的循环操作实现。

简单的单向链表,维护了节点的线程信息。

    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }

查构造器的调用发现,仅在awaitDown方法中调用,即等待任务完成的过程中。而awaitDown方法的调用栈也仅有两处:

 我们再看awaitDown方法的说明 以及源码

    /**
     * 等待任务完成,或者由于中断或超时而放弃
     * 任务完成或者超时则返回对应状态值,线程中断则抛异常
     * 参数timed是否设置超时设置,nacos超时设定等待时间,若timed为false,则nacos为0
     */
    private int awaitDone(boolean timed, long nanos)
            throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (; ; ) {
            //判断当前线程是否已中断,该方法会清除线程状态,也就是说第一次调用返回true,
            //并且没有再次被中断时,第二次调用将返回false
            if (Thread.interrupted()) {
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
            // 任务已完成或被取消
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            // 表示任务马上完成,不必进入等待队列
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
                // 此时s只可能为NEW
            else if (q == null)
                q = new WaitNode();
            else if (!queued)
                // 使用CAS操作加入当前等待节点q,通过将q设为新的栈顶元素,即waiters,
                // 同时修改q.next指针指向上一次的waiters。这里使用自旋操作来保证操作一定成功
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                        q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                LockSupport.parkNanos(this, nanos);
            } else
                LockSupport.park(this);
        }
    }

future状态转换图

五、任务取消

对于cancel方法,着重再分析一下源码

 //有一个入参,需要说明task是否是可中断的
    public boolean cancel(boolean mayInterruptIfRunning) {
        // 任务状态为new,则根据入参true设置为中间态INTERRUPTING或false设置为终止态CANCELLED 
        // 如果上述操作无法完成,说明任务取消失败,返回false
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }

可以看到cancel方法主要做了两件事,1. 修改自身状态值 2. 中断任务执行线程(参数为true时)

取消操作只是改变了任务对象的状态并可能会中断执行线程(这里的中断只是给线程打上一个中断标记,关于线程中断的概念可查阅相关文章)。如果任务的逻辑代码没有响应中断,则会一直异步执行直到完成,只是最终的执行结果不会被通过get方法返回,计算资源的开销仍然是存在的。

对于尚未启动的任务,调用cancle后将永远无法被执行。

六、runAndReset 运行并重置任务

FutureTask一旦执行结束,任务就不能被重启或取消(除非使用runAndReset执行计算)

    /**
     * 执行计算而不设置其结果,所以无法通过get获取到任务结果,同时保持此任务为初始状态,
     * 如果计算遇到异常或被取消,则无法这样再次重启任务
     * 这是专为与 本质上执行多次的任务 一起使用而设计的    
     */
    protected boolean runAndReset() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return false;
        boolean ran = false;
        int s = state;
        try {
            Callable<V> c = callable;
            if (c != null && s == NEW) {
                try {
                    c.call(); // don't set result
                    ran = true;
                } catch (Throwable ex) {
                    setException(ex);
                }
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
        return ran && s == NEW;
    }
高级Salesforce开发面试题 自定义设置允许您创建自定义数据集,以及为组织、配置文件或特定用户创建和关联自定义数据。自定义元数据类似于自定义设置,但是自定义元数据类型中的记录被视为元数据而不是数据。自定义设置有列表和层次自定义设置两种。自定义元数据中没有此类类型。此外,自定义元数据不支持基于用户配置文件或特定用户的层次结构类型的数据。自定义设置现在不再支持关系字段。但您可以在自定义元数据对象之间创建查找。元数据查询不占用查询sql次数的限制。 阅读详情

相关推荐

每天一个优秀提示词学习收藏 - 文本去AI化(四)

宝子们,今天不藏私啦!我把这段时间四处搜罗、潜心钻研出来的文本去AI化Prompt 提示词大礼包双手奉上,这可都是能让你的 AI 创作水平原地起飞的干货!走过路过不要错过,赶紧码住,开启你的 AI 创作进阶之旅吧!

2452

Callable、Future 和 FutureTask经典讲解以及应用!

Callable、Future 和 FutureTask经典讲解以及应用!🎉

qq_56158663的博客 1034

拆解大疆Osmo Pocket:聊聊微型云台里那几根比头发丝还细的线,以及它为啥一开机就‘摇头晃脑’

本文深入拆解大疆Osmo Pocket微型云台的精密硬件结构,揭秘其开机自检时‘摇头晃脑’的奥秘。通过分析无刷电机、霍尔传感器和46AWG同轴线等核心组件,展现了大疆在微型云台设计中的工程智慧与创新技术,特别是FOC控制和陀螺仪的应用,实现了0.1°级的姿态控制精度。

weixin_27869497的博客 671

FutureTask简介

FutureTask的介绍以及使用

u014516601的博客 1492

Excel下拉公式不自动计算?5步彻底解决!

Excel下拉公式不自动计算?5步彻底解决!在使用Excel处理数据时,公式下拉填充是提升效率的核心操作。但许多用户都遇到过这样的困惑:明明设置了公式,下拉时结果却纹丝不动。本文将从技术原理出发,结合5步实操方案,为您彻底解析这个高频问题的根源与解决方案。

mmoo_python的博客 1万+

virtualbox虚拟机里面最顶部没有控制、设置和帮助等选项

无意中操作,导致virtualbox虚拟机里面最顶部没有控制,设置和帮助选项  virtualbox虚拟机窗口显示如下如: 如何设置,窗口显示如下图所示呢: virtualbox虚拟机窗口模式显示设置,切换可以通过如下图所示操作进行切换: PS:一定要是右边的Ctrl+C

WEL测试 8780

FutureTask是什么?

这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

花&败 3331

详解最大似然估计(MLE)、最大后验概率估计(MAP),以及贝叶斯公式的理解

声明:本文为原创文章,发表于nebulaf91的csdn博客。欢迎转载,但请务必保留本信息,注明文章出处。 本文作者: nebulaf91 本文原始地址:最大似然估计(Maximum likelihood estimation, 简称MLE)和最大后验概率估计(Maximum a posteriori estimation, 简称MAP)是很常用的两种参数估计方法,如果不理解这两种方法的思路,很

nebulaf91的博客 33万+

FutureTask详解

接口功能描述主要方法/特点适用场景Runnable定义要执行的任务,无返回值run()用于执行不需要结果的任务,如线程的任务Callable定义要执行的任务,有返回值,并可能抛出异常call()用于执行有返回值的任务Future管理异步计算的结果,检查状态和处理异常get()cancel()isDone()用于管理异步任务的结果和状态结合Runnable和Future的功能,可以执行任务并管理结果继承Runnable和Future用于需要同时支持Runnable和Future功能的场景,如。

落日的博客 3190

从零到英雄:如何用FPGA和AD9288打造你的第一台便携式示波器

本文详细介绍了如何利用FPGA和AD9288 ADC芯片从零开始打造一台高性能便携式示波器。通过硬件架构设计、高速数据采集系统实现及信号处理与显示系统的开发,读者可以掌握400Msps采样率的实现方法,并了解关键器件选型与系统优化技巧。

jjj34的博客 279

手把手教你学simulink(16.2)--基于Simulink的高通滤波器(High-Pass Filter)项目

基于Simulink的高通滤波器(High-Pass Filter)项目的详细实例介绍。这个项目将重点放在高通滤波器的设计、实现和仿真上,帮助你理解和应用高通滤波器在信号处理中的作用。

MHD0815的博客 927

【Java并发编程实战】——FutureTask详解

FutureTask 代表一个可取消的异步计算。

mbtlami 1738

OpenClaw第2章:快速安装

本章将手把手教你安装 OpenClaw。

Maynor的博客 3237

【第十二篇】Java 线程池Future和FutureTask【重点】

1.1 概述   本文主要讲解Java线程池与Future和FutureTask的结合使用,通过本文的学习你可以轻松的在日常开发中运用线程池与Future和FutureTask的使用。 Future的出现原因 我们在Java线程池ThreadPoolExecutor详解中利用execute(Runnable r)方法来异步执行任务,但是有一个缺点,就是无法执行带有返回值的任务。 1.2 原理详解 1.2.1 结构图 FutureTask实现了RunnableFuture接口,而RunnableFutu

ᶻᶻᶻ 枕星河的博客 1万+

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Future和FutureTask的区别

先看下Future、FutureTask相关类的关系 Future只是一个接口,FutureTask是实现了RunnableFuture Future接口可以实现的功能 Future呈现的是异步计算的结果。Future中的方法提供了检查计算是否已经完成,并且等待计算的结果,还能够重新获取计算记得结果。当计算已经完成的时候只能使用get()方法获取结果,如果有需要的话,可以一直阻塞等待结果,直到结...

m0_37148920的博客 1万+

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Future和 FutureTask

Future和 FutureTask

qq_34358193的博客 981

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Java中Future和FutureTask详解及使用

文章目录一、Future 接口二、FutureTask三、使用 Callable 和 Future四、小结(FutureTask核心原理) 一、Future 接口 当 call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用 Future 对象。 将 Future 视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable 返回)。Future 基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写 5 种方法,这

Charte的博客 1万+

理解Future和FutureTask

一、Future简介 Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。 当我们在使用线程池的时候,我们怎么知道阻塞队列的任务的执行情况呢? Future为我们提供了可以获取线程返回值的方法。同时如果这个任务如果执行时间很长,我们可以在提交任务后立刻返回Future,主线...

LJJZJ的博客 324

Future和FutureTask实现类详解以及使用。

Future是Java并发编程中的一个接口,用来表示异步计算的结果。它允许我们提交一个任务,然后之后再去获取结果,或者在结果可用时处理它。我们需要考虑Future的主要方法。根据文档,Future接口有几个关键方法:isDone()检查计算是否完成,get()获取结果(会阻塞直到完成),cancel()尝试取消任务,isCancelled()判断是否被取消。这些方法的作用和用法需要详细说明。然后是Future的实现类。

qq_39126115的博客 1541

FutureTask和Future

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果等操作。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。 Future类位于java.util.concurrent包下,它是一个接口: public interface Future<V> { boolean cancel(boolean mayInter...

LTC_1234的博客 252

java 多线程之Future和FutureTask

任务在Executor线程执行器当中是异步执行的,而有些任务是需要返回执行结果的,故在Executor派生接口ExecutorService接口中定义了带返回结果的提交方法submit,其中返回结果为Future接口的对象JDK中的Future。

weixin_44313232的博客 1152

认识Future和FutureTask

什么是Future Future是一个接口,在java.util.concurrent包下,官方注释解释Future表示异步计算的结果。其提供了一些方法来检查计算是否完成,等待其完成以及检索计算结果。计算完成后,只能使用方法get()方法检索结果,并在必要时阻塞,直到准备好为止。取消是通过cancel()方法执行的。提供了isCancelled()方法来确定任务是正常完成还是被取消。一旦计算完成,就不能取消计算。如果您想使用Future是为了可取消性但不提供可用的结果,则可以声明Future,并由于基础任务

weixin_41197407的博客 549

future和futureTask 的区别

future和futureTask 的区别

w3x3g的博客 2274

java的Future和FutureTask

public interface Future Future 表示异步计算的结果。 Future有个get方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。  Future 主要定义了5个方法:  1)boolean cancel(boolean mayInterruptIfRunning):试图取消对此任务的执行。如果任务已完成

linghu_java的专栏 1万+

Java多线程 Future和FutureTask的区别

Java多线程 Future和FutureTask的区别

Yeats_Liao的博客 4479
上一篇: Future模式介绍
下一篇: Java修饰符
MrMoving
博客等级 码龄10年 54粉丝 145原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值