Java多线程保持顺序性

关于多线程同步的一切:乱序执行和内存屏障 所以CPU0收到Read消息后,会把b的0值发给CPU1,所以继续在`while (b);简而言之,Core执行到write memory barrier(wmb)的时候,如果Store Buffer还有悬而未决的store操作,则都会被mark上,直到被标注的Store操作进入内存后,后续的Store操作才能被执行,因此wmb保障了barrier前后操作的顺序,它不关心barrier前的多个操作的内存序,以及barrier后的多个操作的内存序,是否与Global Memory Order一致。... 阅读详情

只要了解过多线程,我们就知道线程开始的顺序跟执行的顺序是不一样的。如果只是创建三个线程然后执行,最后的执行顺序是不可预期的。这是因为在创建完线程之后,线程执行的开始时间取决于CPU何时分配时间片,线程可以看成是相对于的主线程的一个异步操作。

public class FIFOThreadExample {
    public synchronized static void foo(String name) {
        System.out.print(name);
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> foo("A"));
        Thread thread2 = new Thread(() -> foo("B"));
        Thread thread3 = new Thread(() -> foo("C"));
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

输出结果:ACB/ABC/CBA...

那么我们该如何保证线程的顺序执行呢?

如何保证线程的顺序执行?

1. 使用Thread.join()实现

通过thread的join方法保证多线程的顺序执行, wait是让主线程等待

Thread.join()的作用是让父线程等待子线程结束之后才能继续运行。以上述例子为例,main()方法所在的线程是父线程,在其中我们创建了3个子线程A,B,C,子线程的执行相对父线程是异步的,不能保证顺序性。而对子线程使用Thread.join()方法之后就可以让父线程等待子线程运行结束后,再开始执行父线程,这样子线程执行被强行变成了同步的,我们用Thread.join()方法就能保证线程执行的顺序性。

public class FIFOThreadExample {
    
    public static void foo(String name) {
        System.out.print(name);
    }

    public static void main(String[] args) throws InterruptedException{
        Thread thread1 = new Thread(() -> foo("A"));
        Thread thread2 = new Thread(() -> foo("B"));
        Thread thread3 = new Thread(() -> foo("C"));
        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        thread3.start();
    }
}

输出结果:ABC

2. 使用单线程线程池来实现

ExecutorService executor = Executors.newSingleThreadExecutor()

另一种保证线程顺序执行的方法是使用一个单线程的线程池,这种线程池中只有一个线程,相应的,内部的线程会按加入的顺序来执行。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FIFOThreadExample {

    public static void foo(String name) {
        System.out.print(name);
    }

    public static void main(String[] args) throws InterruptedException{
        Thread thread1 = new Thread(() -> foo("A"));
        Thread thread2 = new Thread(() -> foo("B"));
        Thread thread3 = new Thread(() -> foo("C"));
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.submit(thread1);
        executor.submit(thread2);
        executor.submit(thread3);
        executor.shutdown();
    }
}

输出结果:ABC

3. 使用volatile关键字修饰的信号量实现

3.1JMM-java memory model

解决并发过程中如何处理可见性,原子性,有序性的问题
runnable ,thread。
并发编程中的2个关键问题:
a. 线程间如何通信 --wait() notify() notifyAll()
a) 共享内存 -隐式通信
b) 消息传递 - 显示通信
b. 线程之间如何同步
在共享内存的并发模型中,同步是显示做的; synchronized
在消息传递的并发模型中,由于小时发送必须在消息接受之前,所以同步是隐式的

3.2. 定位内存可见性问题

什么对象是内存共享的 ,什么不是,堆内存的对象,java虚拟机栈

JMM定义了线程和主内存之间的一个关系。线程之间通讯,必须通过主内存通讯。

volatile、synchronized
volatile 功能,声明了volatile的变量,进行写操作的时候,jvm会向处理器发送一条Lock的前缀指令,会把这个变量所在缓存行的数据写回到系统内存。
在多处理器的情况下,保证各个处理器缓存一致性的特点,会实现缓存一致性协议。改变了值的volatile变量,在其他行程被指为失效状态其他线程要使用这个变量,需要重新到驻村里面去取。

synchronized:可重入锁、互斥性、可见性
volatile 可以做到原子性,可见性,不能做到复合操作的原子性

比如: volatile int i;
i++ 读取i的值,给i加1,把新的值赋值给i,这个i++操作在多线程环境下是无法保证原子性的

synchronized后面锁定的对象,可以是一个类对象,也可以是一个成员变量。
第一个线程进来调用monitorener 和monitorexit实现,获取对象的监视器,释放对象监视器
后续进程过来,如果monitor还没有释放,那么他获取不到这个对象的monitor,会放到一个对列里面:synthronizedQueue。 等到对象的锁释放了,通知synthronizedQueue出队列,获取那个monitor。

上面两种的思路都是让保证线程的执行顺序,让线程按一定的顺序执行。这里介绍第三种思路,那就是线程可以无序运行,但是执行结果按顺序执行。
你应该可以想到,三个线程都被创建并start(),这时候三个线程随时都可能执行run()方法。因此为了保证run()执行的顺序性,我们肯定需要一个信号量来让线程知道在任意时刻能不能执行逻辑代码。
另外,因为三个线程是独立的,这个信号量的变化肯定需要对其他线程透明,因此volatile关键字也是必须要的。

public class TicketExample2 {

    //信号量
    static volatile int ticket = 1;
    //线程休眠时间
    public final static int SLEEP_TIME = 1;

    public static void foo(int name){
        //因为线程的执行顺序是不可预期的,因此需要每个线程自旋
        while (true) {
            if (ticket == name) {
                try {
                    Thread.sleep(SLEEP_TIME);
                    //每个线程循环打印3次
                    for (int i = 0; i < 3; i++) {
                        System.out.println(name + " " + i);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //信号量变更
                ticket = name%3+1;
                return;

            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> foo(1));
        Thread thread2 = new Thread(() -> foo(2));
        Thread thread3 = new Thread(() -> foo(3));
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

执行结果:
1 0
1 1
1 2
2 0
2 1
2 2
3 0
3 1
3 2

4. 使用Lock、synchronized和信号量实现

Lock是java5以后出现的,在juc包: java.util.concurrentlocks

4.1 synthronized的锁什么时候释放

    1.1 获取锁的线程执行完了该代码块
    1.2 线程出现异常

4.2 synchronized的缺陷

2.1 读写冲突

4.3 Lock可以主动去释放锁,而synchronized是被动的

ReadWriteLock
ReadLock
WriteLock
ReenterantLock可重入锁
可中断锁
公平锁 等待一个锁的实际越长,获取这个锁的几率越高
ReenterantReadWriteLock 可重入读写锁
Lock是java里面的一个接口,有丰富的实现,而synchronized是java的一个关键字,是一个内置的功能

竞争资源激烈的情况,Lock性能优于synchronized,但是jdk7,8对synchronized做了很多优化,性能差异不大。

此种方法的思想跟第三种方法是一样的,都是不考虑线程执行的顺序而是考虑用一些方法控制线程执行业务逻辑的顺序。这里我们同样用一个原子类型信号量ticket,当然你可以不用原子类型,这里我只是为了保证自增操作的线程安全。然后我们用了一个可重入锁ReentrantLock。用来给方法加锁,当一个线程拿到锁并且标识位正确的时候开始执行业务逻辑,执行完毕后唤醒下一个线程。
这里我们不需要使用while进行自旋操作了,因为Lock可以让我们唤醒指定的线程,所以改成if就可以实现顺序的执行。

public class TicketExample3 {
    //信号量
    AtomicInteger ticket = new AtomicInteger(1);
    public Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private Condition[] conditions = {condition1, condition2, condition3};

    public void foo(int name) {
        try {
            lock.lock();
            //因为线程的执行顺序是不可预期的,因此需要每个线程自旋
            System.out.println("线程" + name + " 开始执行");
            if(ticket.get() != name) {
                try {
                    System.out.println("当前标识位为" + ticket.get() + ",线程" + name + " 开始等待");
                    //开始等待被唤醒
                    conditions[name - 1].await();
                    System.out.println("线程" + name + " 被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(name);
            ticket.getAndIncrement();
            if (ticket.get() > 3) {
                ticket.set(1);
            }
            //执行完毕,唤醒下一次。1唤醒2,2唤醒3
            conditions[name % 3].signal();
        } finally {
            //一定要释放锁
            lock.unlock();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        TicketExample3 example = new TicketExample3();
        Thread t1 = new Thread(() -> {
            example.foo(1);
        });
        Thread t2 = new Thread(() -> {
            example.foo(2);
        });
        Thread t3 = new Thread(() -> {
            example.foo(3);
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

输出结果:
线程2 开始执行
当前标识位为1,线程2 开始等待
线程1 开始执行
1
线程3 开始执行
当前标识位为2,线程3 开始等待
线程2 被唤醒
2
线程3 被唤醒
3

上述的执行结果并非唯一,但可以保证打印的顺序一定是123这样的顺序。

大数据量下,多线程任务处理打乱集合数据原有顺序问题的解决方案 在循环中使用多线程的情况下,不能保证任务的执行顺序与原来的顺序一致。因为多线程的任务在执行过程中是并行的,各个任务的执行完成时间是不确定的。在上述的代码中,使用了executor.submit()方法将任务提交给线程池执行。线程池会根据可用的线程数和任务队列的情况来决定任务的执行顺序。尽管代码中使用了synchronized关键字对list进行了同步控制,但它仅保证了在同一时间只有一个线程可以进行list.add操作。即使保证了同步,不同线程执行完任务后,它们将按照任务完成的顺序尝试添加到list中。 阅读详情

相关推荐

Java面试精选(5):消息队列中,如何保证消息的顺序性?

问:如何保证消息的顺序性? 面试官心理分析 其实这个也是用 MQ 的时候必问的话题,第一看看你了不了解顺序这个事儿?第二看看你有没有办法保证消息是有顺序的?这是生产系统中常见的问题。 面试题剖析 我举个例子,我们以前做过一个 mysqlbinlog同步的系统,压力还是非常大的,日同步数据要达到上亿,就是说数据从一个 mysql 库原封不动地同步到另一个 mysql 库里面去(mysql -> mysql)。常见的一点在于说比如大数据 team,就需要同步一个 mysql 库过来,对公司的业..

Java面试精选的博客 465

线程安全(实现多个线程的有序执行)

飒飒的

lyxnb_的博客 386

RabbitMQ如何保证消息的顺序性【重点】

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。进行两个数据库的数据同步,由于对数据库的数据操作是具有顺序性的,如果操作顺序搞反,就会造成不可估量的错误。,那么原本应该被删除的数据,就没有被删除,造成数据的不一致问题。操作,这个顺序必须是这样,如果在同步过程中,消息的顺序变成了。去消费,这样就会造成顺序的错误,...

web13618542420的博客 1万+

设计模式之单例模式

java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍两种:懒汉式单例、饿汉式单例 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。 //饿汉式 public class Singleton { private static Singleton singleton= new Singleton(); //自动的创建一个唯一的实例 private Sing

罗罗的1024 509

【Java多线程】过程并行执行,结果顺序返回

适用场景 问题抽象:多线程处理集合,返回结果无序。我们经常会遇到需要多线程处理一个有序集合(list)然后返回有序的处理结果,因为多线程的处理开始和结束都是不可预期的,所以结果常常是无序的。 预期目的:并行处理业务逻辑,按顺序返回处理结果 处理方案:多线程处理返回结果Futrue,对Future保证顺序即可得到有序的结果(不考虑资源共享) 在这里插入代码片 ...

weirong2010的博客 2187

如何保证消息的顺序性?

面试题 如何保证消息的顺序性? 面试官心理分析 其实这个也是用 MQ 的时候必问的话题,第一看看你了不了解顺序这个事儿?第二看看你有没有办法保证消息是有顺序的?这是生产系统中常见的问题。 面试题剖析 我举个例子,我们以前做过一个 mysql binlog 同步的系统,压力还是非常大的,日同步数据要达到上亿,就是说数据从一个 mysql 库原封不动地同步到另一个 mysql 库里面去(mysql -> mysql)。常见的一点在于说比如大数据 team,就需要同步一个 mysql 库过来,对公司的业务系

weixin_49794051的博客 544

java多线程-保持顺序性

ThreadOrderTest public class ThreadOrderTest { public static void main(String[] args) { // String s = getKey(); // Optional.ofNullable(s).orElse("").length(); // List<String> list = getList(); // Optional.ofNullable(

program哲学 780

Synchronized和Lock

线程安全的两个方面一个是执行控制和内存可见。执行控制的目的是控制代码执行顺序以及是否可以并发执行,内存可见控制的是线程执行结果在内存中对其它线程的可见性。根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。

Mr.xing的博客 4009

《重学Java高并发》disruptor在数据同步场景下的应用实战(技术方案设计实战)

我们总是喜欢瞻仰大厂的大神们,但实际上大神也不过凡人,与菜鸟程序员相比,也就多花了几分心思,如果你再不努力,差距也只会越来越大。实际上,作为程序员,丰富自己的知识储备,提升自己的知识深度和广度是很有必要的。

2401_84048542的博客 892

十一、机器学习进阶知识:径向基神经网络(RBFNN)的实现(Python,附源码及数据集)

本文对径向基神经网络(RBFNN)的理论基础及建模步骤进行介绍,之后使用Python实现基于RBFNN的数据预测,通俗易懂,适合新手学习,附源码及实验数据集。

weixin_42051846的博客 1万+

电池建模 004- Battery (Table-Based)表格化电池模型入门学习

对于 Battery (Table-Based) 模块,提供了多种内置参数。该预参数化数据允许您将模块设置为代表特定供应商的组件。这些电池的参数化可以与制造数据表中的放电曲线相匹配。要加载预定义的参数化,双击 Battery (Table-Based) 模块,点击 Selected part 参数中的 超链接,然后在块参数化管理器窗口中,从可用组件列表中选择所需部件。 (默认)选项用于参数化模块,以代表特定供应商的组件。

qq_63860930的博客 2734

Java多线程-死锁

第一次加锁能够成功,但第二次加锁时锁已经被占用,只能进行阻塞等待,而第一次加锁又没有走完因此无法释放,导致第二次加锁会一直阻塞等待。sleep()函数是为了确保每个线程已经执行到获取第二把锁的阶段,如果不加,可能会出现某个线程一次性获取到两把锁,顺利执行的情况,不能达成死锁。把嵌套的锁改成并列的锁,使得不会出现一个线程持有一个锁并等待另一个线程持有的锁的情况。一个线程拿到锁后,另一个线程想要再尝试获取锁,必然要阻塞等待,而不是把锁抢过来。两个线程两把锁,每个线程获取到一把锁后,尝试获取对方的锁。

追风赶月莫停留 498

6、基于深度强化学习的无人机路径规划

本文研究了基于深度强化学习的无人机路径规划方法,提出了一种融合改进DQN算法与人工势场法的路径规划策略。通过设计合理的网络结构、状态空间、动作空间和奖励函数,结合ε-贪心策略进行动作选择,实现了无人机在复杂环境中的自主避障与目标导向飞行。仿真实验表明,该方法具有收敛速度快、实时性强和可操作性高等优点,能有效完成室内环境下的路径规划任务。文章还总结了算法优势,并对未来在连续动作、复杂环境适应及多无人机协同等方面的发展方向进行了展望。

web39explorer的博客 166

使用线程顺序打印数字

实现思路:要顺序打印数字,首先要保证打印处于同步的语句块中,这个可以用synchronized实现;其次,可以采用线程顺序来保证数字顺序,在for循环中开启线程,可以保证线程是有序的。 package com.xicheng.bigdata; /** * @author liubin52 * @date 2018/11/6 * @description */ public class...

xichengxml的博客 386

基于深度学习的水质预测:多参数监测与预测

在本篇博客中,我们详细介绍了如何使用深度学习技术处理水质预测问题,包括数据集的准备与预处理、多层感知机模型的构建、模型的训练与评估以及模型优化策略。我们使用 Python 语言基于 TensorFlow 和 Keras 框架实现了模型,并给出了各种模型优化方法及对应代码。通过实践,我们可以发现深度学习技术在水质预测方面具有很好的应用潜力。在实际应用中,我们还可以根据具体问题和数据集进一步调整和优化模型,以获得更好的预测性能。

斌擎科技 1080

背靠背VSC直流母线电压控制与同步发电机并网发散问题:原理、分析与解决方案

背靠背VSC直流母线电压控制与同步发电机并网发散问题分析 摘要: 本文研究了背靠背VSC系统在整流侧由理想三相电源切换为同步发电机时出现的直流母线电压振荡和发散问题。通过理论分析发现,锁相环同步失稳、等效阻抗失配和直流母线电容参数失配是导致系统失稳的关键因素。当整流侧接入同步发电机后,系统形成正反馈振荡回路,同时等效电感增大导致电流环响应速度降低,直流侧电容容量不足无法吸收发电机功率波动。文章提出了从跟网型控制到构网型VSG控制的完整升级方案,包含详细的MATLAB/Simulink建模和控制算法实现,并通

huanghm88的专栏 121

java关键字volatile

volatile关键字可以确保多个线程之间对共享变量的可见性,即当一个线程修改了共享变量的值,其他线程可以立即看到这个修改。// 然而,volatile关键字并不能保证操作的原子性,// 也就是说,如果多个线程同时对共享变量进行操作,可能会导致数据不一致的问题。

qq_61103482的博客 350

【我的Android进阶之旅】解决android应用使用android:persistent属性带来的系统Launcher应用必须重启设备才生效的问题,大幅度提升开发效率

一、问题描述 我们在开发系统级别应用,比如Launcher桌面的时候,会发现有个问题:每次Launcher修改完代码,重新编译之后,安装到android设备上之后,必须重启才会生效。否则就是使用了之前的Launcher版本的内容。而每次重启设备得一分钟左右,太慢了,太影响效率了! 1.1 痛点 每次更新完代码,重新安装之后,必须重启设备才会生效。效率太TM低了! 二、分析问题 经过一轮代码排查,最终发现是因为src\main\AndroidManifest.xml文件中,定义application使用了an

字节卷动 1712

线程死锁

线程死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 线程死锁发生的必要条件: 1、互斥性:在同一时间,资源只能被一个线程占用,其它线程要请求资源只能等待。 2、不剥夺性:线程获得的资源不能被其它线程强行夺走,只能就该线程主动释放。 3、请求和保持:线程至少获得了一个资源,然后又请求新的资源,但是对自己获得的资源保持不放。 4、循环等待:多个线程之间形成一种收尾相连的循环等待资源的关系。 那么我们如何判断死锁呢?

tantianoo的博客 130

【数据集】全球陆地表面反照率Albedo

【数据集】全球陆地表面反照率Albedo

WW、forever的博客 1453

Arduino I2C for TCA9548A应答扫描程序

Arduino I2C for TCA9548A应答扫描程序

perseverance51博客 1119

Java多线程中顺序打印

方法特点使用volatile关键字适用于简单的顺序控制,代码简单,但不适用于复杂的线程同步和互斥情况。使用Lock和Condition提供了更丰富的线程同步和互斥机制,并可以精确控制线程的等待和唤醒,适用于复杂的线程交互情况。使用Semaphore通过信号量来控制线程的获取和释放,并可以灵活地控制线程的顺序,适用于多种线程同步场景。

1668

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

RabbitMQ--消息顺序性

本文介绍了RabbitMQ中保证消息顺序性的多种方案。主要内容包括:1)消息顺序性概念及RabbitMQ的顺序保证机制;2)保证顺序性的四种方式:单队列单消费者、消息确认机制、QoS设置和业务分区设计;3)通过原生Java和SpringBoot两种方式实现顺序消费的代码示例;4)重点介绍了多消费者分区顺序方案,通过订单ID哈希将消息分配到不同队列,每个队列由独立消费者处理,实现并发消费与分区内顺序保证。文章指出,严格顺序场景推荐单队列单消费者或分区队列方案,并详细展示了SpringBoot实现分区顺序消费的

你我约定有三的博客 1775

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Java中的阻塞队列被异步消费怎么保持顺序

在此代码中,任务的顺序是通过Task类的taskId来保持的。阻塞队列中的任务可以被多个消费者线程异步地消费。每个消费者线程从队列中获取任务,处理时会按照taskId顺序来消费任务。即使是多个消费者线程,只要任务本身具有顺序信息,并且taskId比较能保证顺序,消费者会依然能够保持任务的顺序。通过这种方式,可以确保异步消费的情况下,任务依然按照某个规则(如任务ID)进行处理。

有时间就写写代码 682

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

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

java 实际生产中多线程、高并发顺序性的处理

多线程、并发顺序性的问题其实很常见。拿个实际项目中的消息推送举个例子,在12点整的时候,有3个事件同时触发了:1、到点领外卖券2、商家折扣3、附件推荐的新品,这几个都是不同商家发起的http请求,并要推送到用户手机上。2、如果是多个商家同时推送消息,项目只要保证单个商家的消息是顺序性的,可以选择直接锁住商家的id,这样其他商家发的时候也不会阻塞,只阻塞单个商家的消息。如果不考虑并发正常写个controller处理,用户手机上收到消息大概率就是乱序的,有的用户可能收到是3、2、1,有的可能是1、3、2。

qq_40505914的博客 763

Python多线程如何并发执行却保持顺序输出?实战示例解析!

Python多线程的并发和顺序输出并不矛盾!并发执行:提高程序运行效率。顺序输出:满足业务逻辑需求。如果你对Python多线程的更多高级用法感兴趣(比如锁机制、线程间通信),或者想学习其他编程技巧,记得关注【程序员总部】,获取更多实战干货!

字节王德发 1270
上一篇: consul、eureka、nacos对比
下一篇: 六款主流ETL工具介绍及功能对比
wrr-cat
博客等级 码龄9年 128粉丝 302原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值