Java 多线程:并发编程的三大特性

马士兵-郑金维—并发编程—2.并发编程的三大特性 并发编程特性 阅读详情

可见性

所谓线程数据的 可见性 ,指的就是内存中的某个数据,假如第一个 CPU 的一个核读取到了,和其他的核读取到这个数据之间的可见性。

每个线程会保存一份拷贝到线程本地缓存,使用 volatile ,可以保持线程之间数据可见性。

如下示例

package git.snippets.juc;


import java.util.concurrent.TimeUnit;

/**
 * 并发编程三大特性之:可见性
 *
 * @author <a href="mailto:410486047@qq.com">Grey</a>
 * @since 1.8
 */
public class ThreadVisible {

    static volatile boolean flag = true;

    public static void main(String[] args) throws Exception {
        Thread t = new Thread(() -> {
            System.out.println(Thread.currentThread() + " t start");
            while (flag) {
                // 如果这里调用了System.out.println()
                // 会无论flag有没有加volatile,数据都会同步
                // 因为System.out.println()背后调用的synchronized
                // System.out.println();
            }
            System.out.println(Thread.currentThread() + " t end");
        });
        t.start();
        TimeUnit.SECONDS.sleep(3);
        flag = false;


        // volatile修饰引用变量
        new Thread(a::m, "t2").start();
        TimeUnit.SECONDS.sleep(2);
        a.flag = false;

        // 阻塞主线程,防止主线程直接执行完毕,看不到效果
        System.in.read();
    }

    private static volatile A a = new A();

    static class A {
        volatile boolean flag = true;

        void m() {
            System.out.println("m start");
            while (flag) {
            }
            System.out.println("m end");
        }
    }
}

代码说明:

  • volatile 修饰了 flag 变量,主线程改了 flag 的值,子线程可以感知到;

  • 如在上述代码的死循环中增加了 System.out.println() , 则会强制同步 flag 的值,无论 flag 本身有没有加 volatile ;

  • 如果 volatile 修饰一个引用对象,如果对象的属性(成员变量)发生了改变, volatile 不能保证其他线程可以观察到该变化。

关于三级缓存

如上图,内存读出的数据会在 L3,L2,L1 上都存一份。

在从内存中读取数据的时候,根据的是程序局部性的原理,按块来读取,这样可以提高效率,充分发挥总线 CPU 针脚等一次性读取更多数据的能力。

所以这里引入了一个缓存行的概念,目前一个缓存行多用 64个字节 来表示。

如何来验证 CPU 读取缓存行这件事,我们可以通过一个示例来说明:

package git.snippets.juc;

/**
 * 缓存行对齐
 *
 * @author <a href="mailto:410486047@qq.com">Grey</a>
 * @since 1.8
 */
public class CacheLinePadding {
    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(() -> {
            for (long i = 0; i < 1000_0000L; i++) {
                arr[0].x = i;
            }
        });

        Thread t2 = new Thread(() -> {
            for (long i = 0; i < 1000_0000L; i++) {
                arr[1].x = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start) / 100_0000);
        System.out.println("arr[0]=" + arr[0].x + " arr[1]=" + arr[1].x);
    }

    private static class Padding {
        public volatile long p1, p2, p3, p4, p5, p6, p7;
    }

    // T这个类extends Padding与否,会影响整个流程的执行时间,如果继承了,会减少执行时间,
    // 因为继承Padding后,arr[0]和arr[1]一定不在同一个缓存行里面,所以不需要同步数据,速度就更快一些了。
    private static class T /*extends Padding*/ {
        public volatile long x = 0L;
    }
}

 

代码说明

以上代码, T 这个类继承 Padding 类与否,会影响整个流程的执行时间,如果继承了,会减少执行时间,因为继承 Padding 后, arr[0] 和 arr[1] 一定不在同一个缓存行里面,所以不需要同步数据,速度就更快一些了。

Java SE 1.8 增加了一个注解 @Contended ,标注后就不会在同一缓存行, 但是这个注解仅适用于 Java SE 1.8,而且还需要增加 JVM 参数 -XX:-RestrictContended

CPU 为每个缓存行标记四种状态(使用两位)

M: 被修改(Modified)

该缓存行只被缓存在该 CPU 的缓存中,并且是被修改过的( dirty ),即与主存中的数据不一致,该缓存行中的内存需要在未来的某个时间点(允许其它 CPU 读取请主存中相应内存之前)写回( write back )主存。

当被写回主存之后,该缓存行的状态会变成独享( exclusive )状态。

E: 独享的(Exclusive)

该缓存行只被缓存在该 CPU 的缓存中,它是未被修改过的( clean ),与主存中数据一致。该状态可以在任何时刻当有其它 CPU 读取该内存时变成共享状态( shared )。

同样地,当 CPU 修改该缓存行中内容时,该状态可以变成 Modified 状态。

S: 共享的(Shared)

该状态意味着该缓存行可能被多个 CPU 缓存,并且各个缓存中的数据与主存数据一致( clean ),当有一个 CPU 修改该缓存行中,其它 CPU 中该缓存行可以被作废(变成无效状态( Invalid ))。

I: 无效的(Invalid)

该缓存是无效的(可能有其它 CPU 修改了该缓存行)。

有序性

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排。

为什么指令重排序可以提高性能?

简单地说,每一个指令都会包含多个步骤,每个步骤可能使用不同的硬件。因此, 流水线技术 产生了,它的原理是:指令1还没有执行完,就可以开始执行指令2,而不用等到指令1执行结束之后再执行指令2,这样就大大提高了效率。

但是,流水线技术最害怕 中断 ,恢复中断的代价是比较大的,所以我们要想尽办法不让流水线中断。指令重排就是减少中断的一种技术。

我们分析一下下面这个代码的执行情况:

a = b + c;
d = e - f ;

先加载b、c( 注意,既有可能先加载b,也有可能先加载c ),但是在执行 b + c 的时候,需要等待 b、c 装载结束才能继续执行,也就是增加了停顿,那么后面的指令也会依次有停顿,这降低了计算机的执行效率。

为了减少这个停顿,我们可以先加载 e 和 f ,然后再去加载 b + c ,这样做对程序(串行)结果是没有影响的,但却减少了停顿:既然 b + c 需要停顿,那还不如去做一些有意义的事情。

综上所述, 指令重排对于提高 CPU 处理性能十分必要。虽然由此带来了乱序的问题,但是这点牺牲是值得的。

指令重排一般分为以下三种:

第一种:编译器优化重排

编译器在 不改变单线程程序语义 的前提下,可以重新安排语句的执行顺序。

第二种:指令并行重排

现代处理器采用了指令级并行技术来将多条指令重叠执行。如果 不存在数据依赖性 (即后一个执行的语句无需依赖前面执行的语句的结果),处理器可以改变语句对应的机器指令的执行顺序。

第三种:内存系统重排

由于处理器使用缓存和读写缓存冲区,这使得加载( load )和存储( store )操作看上去可能是在乱序执行,因为三级缓存的存在,导致内存与缓存的数据同步存在时间差。

指令重排可以保证串行语义一致,但是没有义务保证多线程间的语义也一致。所以在多线程下,指令重排序可能会导致一些问题。

乱序存在的条件是:不影响单线程的最终一致性( as - if - serial )

验证乱序执行的程序示例

package git.snippets.juc;

/**
 * 并发编程的三大特性之:有序性
 *
 * @author <a href="mailto:410486047@qq.com">Grey</a>
 * @since 1.8
 */
public class DisOrder {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    // 以下程序可能会执行比较长的时间
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        for (; ; ) {
            i++;
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            Thread one = new Thread(() -> {
                // 由于线程one先启动,下面这句话让它等一等线程two. 读着可根据自己电脑的实际性能适当调整等待时间.
                shortWait(100000);
                a = 1;
                x = b;
            });

            Thread other = new Thread(() -> {
                b = 1;
                y = a;
            });
            one.start();
            other.start();
            one.join();
            other.join();
            String result = "第" + i + "次 (" + x + "," + y + ")";
            if (x == 0 && y == 0) {
                // 出现这个分支,说明指令出现了重排
                // 否则不可能 x和y同时都为0
                System.err.println(result);
                break;
            } else {
                // System.out.println(result);
            }
        }
    }

    public static void shortWait(long interval) {
        long start = System.nanoTime();
        long end;
        do {
            end = System.nanoTime();
        } while (start + interval >= end);
    }
}

代码说明:

如上示例,如果指令不出现乱序,那么 x 和 y 不可能同时为 0,通过执行这个程序可以验证出来,在我本机测试的结果是:

执行到第 385634 次 出现了 x 和 y 同时为 0 的情况,说明出现了乱序。

原子性

程序的原子性是指整个程序中的所有操作,要么全部完成,要么全部失败,不可能滞留在中间某个环节;在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程所打断。

一个示例:

class T {   
    m =9;
}

 对象 T 在创建过程中,背后其实是包含了多条执行语句的,由于有 CPU 乱序执行的情况,所以极有可能会在初始化过程中生成以一个半初始化对象 t,这个 t 的 m 等于 0(还没有来得及做赋值操作)

所以,不要在某个类的构造方法中启动一个线程,这样会导致 this 对象逸出:因为这个类的对象可能还来不及执行初始化操作,就启动了一个线程,导致了异常情况。

volatile 一方面可以保证线程数据之间的可见性,另外一方面,也可以防止类似这样的指令重排,所以,单例模式中, DCL 方式的单例一定要加 volatile 修饰:

public class Singleton6 {
    private volatile static Singleton6 INSTANCE;
 
    private Singleton6() {
    }
 
    public static Singleton6 getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton6.class) {
                if (INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Singleton6();
                }
            }
        }
        return INSTANCE;
    }
}

Java多线程并发编程系列-02 并发编程的三大特性:原子性、可见性、有序性 并发编程的三大特性之一是原子性,指一个操作不可分割、不可中断。Java通过JMM(Java内存模型)屏蔽硬件差异,保证跨平台一致性。原子性问题常出现在多线程操作共享变量时,如count++操作实际分三步执行,导致结果不符合预期。保证原子性的方法包括:1)synchronized关键字同步代码块;2)CAS(比较交换)操作,如AtomicInteger类,但存在ABA问题和自旋过长问题;3)Lock锁如ReentrantLock;4)ThreadLocal让线程操作各自数据。其中CAS可通过AtomicSta 阅读详情

相关推荐

多线程——并发三特性

原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。原子性多线程并发操作共享变量时,不能保证以下动作的原子性操作,例如:1.先从主内存中,“读” 取最新count数据,至工作内存中2.在本线程的工作内存中,进行 count++ “运算”3.将工作内存中的数据,“写” 回主内存中。

weixin_65572588的博客 1164

【Java】保证并发安全的三大特性

并发编程这三大特性就是为了在多个线程交替执行任务的过程中保证线程安全性。之所以会出现并发编程的三大特性,就是因为在提升程序性能的同时需要保证安全性,而原子性、可见性、有序性这三大特性可以认为是线程安全的等价概念,我们需要通过一些机制来保证这三大特性,也就是保证线程安全。原子性:一次或多次操作在执行期间不被其他线程影响可见性:当一个线程在工作内存修改了变量,其他线程能立刻知道有序性JVM对指令的优化会让指令执行顺序改变,有序性是禁止指令重排。

qq_44544908的博客 1186

嵌入式音频架构 - AudioWeaver 整体概念

AudioWeaver 是DSP Concept公司推出的基于嵌入式平台的音频框架。由AWE Core embedded processing engine 和 Audio Weaver Designer IDE 组成。 Audio Weaver Designer 有三个版本,ST Edition, Standard Edtion 和 Pro Edtion。 Audio Weaver Designer界面: Audio Weaver 的整体使用架构: AWD算法流文件,AWS配置脚本文件,AWB嵌入式b

甲客实验室 6881

【Java 并发】三大特性

synchronized 具有: 原子性, 有序性, 可见性。volatile 具有:有序性,可见性。synchronized 是否保证有序性呢?从上面的双重检测看起来, synchronized 貌似不保证有序性, 但是 synchronized 还是保证有序性的, 只是和 volatile 的有序性不一样。volatile 关键字禁止 JVM 编译器和处理器对其进行重排序, 而 synchronized 保证的有序性是只有单线程可以获取锁, 串行地执行同步代码。

LCN29的博客 1130

爱加密SFSecurity加固逆向分析:从Frida Hook到DEX/SO解密实战

在移动应用安全领域,代码加固是保护核心逻辑与数据的关键技术。其基本原理是通过加密、混淆和运行时保护等手段,防止应用被逆向分析与篡改。这项技术的核心价值在于为开发者提供商业级的安全保障,尤其适用于金融、游戏、阅读等对代码安全要求较高的应用场景。以爱加密企业版SFSecurity方案为例,它构建了从DEX到Native层的立体化防护体系。本次实战通过Frida动态插桩技术,深入剖析了如何系统性地绕过其反调试机制,并精准Hook解密函数,成功从内存中Dump出被保护的原始DEX与SO文件,完整还原了应用逻辑,为安

weixin_42539414的博客 892

MoveIt2 Servo实战:解锁UR机械臂在ROS2中的实时遥操作与动态响应

本文深入探讨了MoveIt2 Servo在UR机械臂实时遥操作中的应用,展示了如何通过ROS2实现毫秒级动态响应。文章详细解析了Servo的核心价值、环境配置、控制器适配及高级调优技巧,特别适用于需要高精度控制的工业场景,如VR手术训练和汽车装配线。

weixin_29044157的博客 250

Java并发三大特性(可见性、有序性、原子性)

Java并发三大特性(可见性、有序性、原子性)

weixin_45370502的博客 748

M系列芯片MacBook上UTM虚拟机部署达梦DM8的避坑指南

本文详细介绍了在M系列芯片MacBook上使用UTM虚拟机部署达梦数据库DM8的完整流程与避坑指南。针对ARM架构环境,重点解决了虚拟机选择、麒麟系统安装、系统调优及DM8命令行安装中的常见问题,为开发者提供了稳定可靠的国产数据库本地测试方案。

weixin_28455801的博客 460

java并发编程:可见性、原子性、有序性三大特性详解

特性Atomic变量volatile关键字Lock接口synchronized关键字原子性可以保障无法保障可以保障可以保障可见性可以保障可以保障可以保障可以保障有序性无法保障一定程度保障可以保障可以保障。

初念初恋的博客 1918

拆解汽车T-Box:从4G模块到MCU,手把手教你理解车联网的‘黑匣子’

本文深入拆解汽车T-Box,揭示其作为车联网‘黑匣子’的核心架构与工作原理。从4G模块、MCU主控到外设接口,详细解析各组件功能及协同机制,并通过实战案例展示故障诊断与性能优化技巧,帮助读者全面理解T-Box在智能汽车中的关键作用。

weixin_30955617的博客 497

【知识图谱】实践篇——基于知识图谱的《红楼梦》人物关系可视化及问答系统实践:part6基于图谱的问答实现

前序文章:本部分则是整个项目的核心内容——基于图谱的问答。需要实现的是对于“简单问题”进行解析,然后从图谱数据库中查询与该人员及其相关人员信息,返回到前端并显示该人员的基本信息。除此之外,用户可以点击返回的结果人员节点,页面能够显示该用户的个人基本信息。那么现在就开始吧。根据源码实现问答形式,其实是比较简单的。原代码中使用ltp对问题进行分词,选取具有名次词性的关键词进行处理。 ltp配置稍许麻烦,我使用jieba分词工具对问题进行分词,然后再按照原文提取问句中人物及关系,然后构建cypher neo4j查

让算法融入生活,改变生活! 2939

Java 多线程(二):并发编程的三大特性

对象 T 在创建过程中,背后其实是包含了多条执行语句的,由于有 CPU 乱序执行的情况,所以极有可能会在初始化过程中生成以一个半初始化对象 t,这个 t 的 m 等于 0(还没有来得及做赋值操作)在从内存中读取数据的时候,根据的是程序局部性的原理,按块来读取,这样可以提高效率,充分发挥总线 CPU 针脚等一次性读取更多数据的能力。,指的就是内存中的某个数据,假如第一个 CPU 的一个核读取到了,和其他的核读取到这个数据之间的可见性。,恢复中断的代价是比较大的,所以我们要想尽办法不让流水线中断。

Practitioner 453

在SAP系统中创建物料 860000000000000082(物料描述为“8680月球广寒宫坦克车”)时,系统报出了一个错误

摘要:SAP系统创建物料"8680月球广寒宫坦克车"时出现"C+020"错误,提示评估区域C916未在物料分类账中设置为生产性。解决方案需进入"会计-控制-产品成本控制-实际成本核算"路径,将评估区域C916手动设置为生产性状态。该操作需由具备权限的系统管理员或FICO顾问执行,建议操作前进行数据备份或在测试环境验证。完成设置后即可重新创建物料。

金牌架构师 394

【Java】多线程和高并发编程(二):并发编程的三大特性

不同的硬件和不同的操作系统在内存上的操作有一定差异的。Java为了解决相同代码在不同操作系统上出现的各种问题,用JMM屏蔽掉各种硬件和操作系统带来的差异。让Java的并发编程可以做到跨平台。JMM规定所有变量都会存储在主内存中,在操作的时候,需要从主内存中复制一份到线程内存(CPU内存),在线程内部做计算。然后再写回主内存中(不一定!原子性的定义:原子性指一个操作是不可分割的,不可中断的,一个线程在执行时,另一个线程不会影响到他。i < 100;i++) {

专注上位机开发、机器视觉、机器学习、嵌入式软硬件开发 1万+

Allegro174版本如何关闭模块复用后铜皮自动从动态变成静态操作指导

在用Allegro进行PCB设计的时候,模块复用是使用的十分频繁的操作,当Allegro升级到了174 S034版本的时候,当使用模块复用的功能的时候,模块内的铜皮会自动动静转换,大部分情况是不需要的。版本如何关闭模块复用后铜皮自动从动态变成静态操作指导。如何关闭这个功能,具体操作如下。

weixin_54787054的博客 1262

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

死磕Java多线程(二)--- 多线程必须了解的三大特性

引言: 我们都知道并发程序正确地执行,必须要保证原子性、可见性以及有序性。只要有一个没有被保证,就有可能会导致程序运行不正确。 下面让我们来看看原子性、可见性以及有序性到底是什么? 可见性 可见性指的是一个线程对变量的写操作对其他线程后续的读操作可见。由于现代CPU都有多级缓存,CPU的操作都是基于高速缓存的,而线程通信是基于内存的,这中间有一个Gap, 可见性的关键还是在对变量的写操作之后能够在...

盐二白 256

OV9734 DATASHEET,完整规格书

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

并发编程特性,悲观锁与乐观锁

并发编程的三大特性:原子性,有序性,可见性 Volatile 悲观锁 乐观锁 ReentrantLock

weixin_44916741的博客 271

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

Java并发编程的三大特性:原子性、可见性、有序性

并发是什么呢? 并发指的是Java中有多线程并发运行。 为什么会有并发? 因为一个线程在执行的过程中不仅会用到cpu资源,还会用到IO,也就是输入输出。IO的速度远远比不上CPU的运算速度。如果线程在IO的时候,不放弃CPU资源,就会导致CPU空闲,以至于CPU的利用率十分低下。所以就引入了多线程,当一个线程要请求IO的时候,会放弃cpu资源。这个时候,其他线程可以使用cpu。这就提高了cpu的...

qq_40276626的博客 78

JMM&并发三大特性

Java Memory Mode,java内存模型,java多线程通信模型涉及到维度:JAVA 层面 JVM层面 硬件层面需要重点掌握理解并发的三大特性,JMM工作内存和主存的关系,多线程是如何通信的,volatile保证可见性和有序性,CAS。

qq_44970261的博客 172

多线程特性

三大特性 原子性、可见性、有序性。 原子性 原子性,即一个操作或者多个操作要么都执行,且不会被任何因素打断,否则都不执行。 可见性 可见性是指多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。 有序性 程序执行的顺序按照代码的先后顺序执行。 多线程控制类 为了保证多线程的三个特性,Java引入了很多线程控制机制,下面介绍其中常用的几种: Threa...

Hydra的博客 400

JAVA并发编程synchronized全能王的原理

说到JAVA并发,相信很多人第一印象想到的就是synchronized,然后就是volatile、JUC、CAS、线程池、AQS、阻塞队列等等这些关键字工具类、原理思想。但这些都离不开并发编程的三大特性:原子性、可见性、有序性。

拉丁解牛技术专栏 1405

并发编程-01 聊透JMM&并发三大特性

1、JMM 1.1 JMM(JAVA多线程通信模型——共享内存模型) 分析Jvm模型,涉及多个层面的知识,需要从以下三个层面一起来分析。 java层面 jvm层面 硬件层面 抽象的JMM模型 2、并发三大特性(并发编程Bug的源头) 2.1 可见性 当一个线程修改了共享变量的值,其他线程能够看到修改的值,则具有可见性。 Java 内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性的。 可见性保障方式: 通过 volatile

csfun1的博客 5312

java并发三大特性--有序性、可见性、原子性

在java并发编程中,我们一般会说需要满足以下三个基本条件才能构成java并发的基础,这三个特性分别是:有序性、可见性、原子性。那么这三个特性到底是一个怎么样的概念呢,下面我们将通过一些例子来详细说明一下这几个概念。 有序性:有序性是指在java并发中,如果执行编码的两条指令按照代码的先后顺序执行。设想一下,如果代码的执行顺序和我们看到的代码顺序不一样,那很可能会导致结果错乱。可为什么会导致代码执行和我们编写看到的不一样呢?这是因为编译器,处理器为了提升执行效率会对代码编译过后的执行指令进行重新排序(指令

rqc112233的专栏 1218

Java 并发系列(一):多线程三大特性

概述 多线程三大特性:原子性、可见性、有序性。 1. 原子性 原子性是指:多个操作作为一个整体,不能被分割与中断,也不能被其他线程干扰。如果被中断与干扰,则会出现数据异常、逻辑异常。 多个操作合并的整体,我们称之为复合操作。一个复合操作,往往存在前后依赖关系,后一个操作依赖上一个操作的结果。如果上一个操作结果被其他线程干扰,对于当前线程看来整个复合操作的结果便不符合预期。同理线程也不能在复合操作中间被中断,中断必须发生在进入复合操作之前或者等到复合操作结束之后。 保证原子性就是在多线程环境下,保证

TroyLemon的专栏 1721

JUC并发编程(一):Java内存模型(JMM)及三大特性:可见性、有序性、原子性

在当今高流量、高并发的互联网业务场景下,并发编程技术显得尤为重要,不管是哪一门编程语言,掌握并发编程技术是个人进阶的必经之路。时隔一个半月没有写技术博客文章,有点生疏了。。。闲话少叙,接下来我将围绕并发编程知识点进行总结讲解,这里从并发编程入门开始,讲述Java内存模型和并发的三大特性。

weixin_41821642的博客 271
上一篇: Java 进阶多线程(二)
下一篇: springBoot异步线程,父子线程数据传递的5种姿势
java_lujj
博客等级 码龄4年 18粉丝 152原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值