并发编程修炼四:synchronized

将Qtcreator编译器、调试器等配置为MSVC2017/2019 阅读详情

作者:海子
    
出处:http://www.cnblogs.com/dolphin0520/

目录:
一.什么时候会出现线程安全问题?

  二.如何解决线程安全问题?

  三.synchronized同步方法或者同步块

一.什么时候会出现线程安全问题?

在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等,而当多个线程同时访问同一个资源的时候,就会存在一个问题:

  由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错。

  举个简单的例子:

  现在有两个线程分别从网络上读取数据,然后插入一张数据库表中,要求不能插入重复的数据。

  那么必然在插入数据的过程中存在两个操作:

  1)检查数据库中是否存在该条数据;

  2)如果存在,则不插入;如果不存在,则插入到数据库中。

  假如两个线程分别用thread-1和thread-2表示,某一时刻,thread-1和thread-2都读取到了数据X,那么可能会发生这种情况:

  thread-1去检查数据库中是否存在数据X,然后thread-2也接着去检查数据库中是否存在数据X。

  结果两个线程检查的结果都是数据库中不存在数据X,那么两个线程都分别将数据X插入数据库表当中。

  这个就是线程安全问题,即多个线程同时访问一个资源时,会导致程序运行结果并不是想看到的结果。

  这里面,这个资源被称为:临界资源(也有称为共享资源)。

  也就是说,当多个线程同时访问临界资源(一个对象,对象中的属性,一个文件,一个数据库等)时,就可能会产生线程安全问题。

  不过,当多个线程执行一个方法,方法内部的局部变量并不是临界资源,因为方法是在栈上执行的,而Java栈是线程私有的,因此不会产生线程安全问题。

二.如何解决线程安全问题?

那么一般来说,是如何解决线程安全问题的呢?

  基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。

  通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。

  在Java中,提供了两种方式来实现同步互斥访问:synchronized和Lock。

  本文主要讲述synchronized的使用方法,Lock的使用方法在下一篇博文中讲述。

三.synchronized同步方法或者同步块

在了解synchronized关键字的使用方法之前,我们先来看一个概念:互斥锁,顾名思义:能到达到互斥访问目的的锁。

  举个简单的例子:如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。

  在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。

  在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

  下面通过几个简单的例子来说明synchronized关键字的使用:

  1.synchronized方法

  下面这段代码中两个线程分别调用insertData对象插入数据:

public class Test {

    public static void main(String[] args)  {
        final InsertData insertData = new InsertData();

        new Thread() {
            public void run() {
                insertData.insert(Thread.currentThread());
            };
        }.start();


        new Thread() {
            public void run() {
                insertData.insert(Thread.currentThread());
            };
        }.start();
    }  
}

class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();

    public void insert(Thread thread){
        for(int i=0;i<5;i++){
            System.out.println(thread.getName()+"在插入数据"+i);
            arrayList.add(i);
        }
    }
}

 此时程序的输出结果为:

这里写图片描述

说明两个线程在同时执行insert方法。

而如果在insert方法前面加上关键字synchronized的话,运行结果为:

class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();

    public synchronized void insert(Thread thread){
        for(int i=0;i<5;i++){
            System.out.println(thread.getName()+"在插入数据"+i);
            arrayList.add(i);
        }
    }
}

这里写图片描述

 从上输出结果说明,Thread-1插入数据是等Thread-0插入完数据之后才进行的。说明Thread-0和Thread-1是顺序执行insert方法的。

这就是synchronized方法。

  不过有几点需要注意:

  1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。

  2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,

  3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。

  2.synchronized代码块

  synchronized代码块类似于以下这种形式:

    synchronized(synObject) {

    }

当在某个线程中执行这段代码块,该线程会获取对象synObject的锁,从而使得其他线程无法同时访问该代码块。

  synObject可以是this,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁。

  比如上面的insert方法可以改成以下两种形式:

class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();

    public void insert(Thread thread){
        synchronized (this) {
            for(int i=0;i<100;i++){
                System.out.println(thread.getName()+"在插入数据"+i);
                arrayList.add(i);
            }
        }
    }
}
class InsertData {
    private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Object object = new Object();

    public void insert(Thread thread){
        synchronized (object) {
            for(int i=0;i<100;i++){
                System.out.println(thread.getName()+"在插入数据"+i);
                arrayList.add(i);
            }
        }
    }
}

从上面可以看出,synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。

  另外,每个类也会有一个锁,它可以用来控制对static数据成员的并发访问。

  并且如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。

看下面这段代码就明白了:

public class Test {

    public static void main(String[] args)  {
        final InsertData insertData = new InsertData();
        new Thread(){
            @Override
            public void run() {
                insertData.insert();
            }
        }.start(); 
        new Thread(){
            @Override
            public void run() {
                insertData.insert1();
            }
        }.start();
    }  
}

class InsertData { 
    public synchronized void insert(){
        System.out.println("执行insert");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行insert完毕");
    }

    public synchronized static void insert1() {
        System.out.println("执行insert1");
        System.out.println("执行insert1完毕");
    }
}

执行结果;
这里写图片描述

  第一个线程里面执行的是insert方法,不会导致第二个线程执行insert1方法发生阻塞现象。

  下面我们看一下synchronized关键字到底做了什么事情,我们来反编译它的字节码看一下,下面这段代码反编译后的字节码为:

public class InsertData {
    private Object object = new Object();

    public void insert(Thread thread){
        synchronized (object) {

        }
    }

    public synchronized void insert1(Thread thread){

    }

    public void insert2(Thread thread){

    }
}

这里写图片描述

从反编译获得的字节码可以看出,synchronized代码块实际上多了monitorenter和monitorexit两条指令。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,其实这个与操作系统里面的PV操作很像,操作系统里面的PV操作就是用来控制多个线程对临界资源的访问。对于synchronized方法,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。

  有一点要注意:对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。

ps:在这里感谢作者海子的笔记,转载记录下

Java进阶路线图:从JVM、并发编程到微服务实战的完整修炼指南 Java进阶的本质是完成从“会用框架”到“读懂原理”的跃迁,难点不在API而在底层机制。理解JVM的类加载、内存模型与垃圾回收,是定位线上OOM、Full GC和性能瓶颈的前提;掌握并发编程中synchronized与AQS的差异、线程池参数设计与可见性规则,才能写出高并发下可靠的服务端代码。再结合Spring Bean生命周期、Redis缓存一致性、MQ削峰与微服务治理等实战场景,把散落的知识连成体系,就能在压测优化、故障排查和架构改造中形成可复用的方法论。围绕基础、中间件、微服务与项目实战逐层推进,正是 阅读详情

相关推荐

Java八股文面试核心考点梳理:从JVM到Kafka的工程师修炼指南

在Java技术面试中,八股文常被误解为死记硬背,实则是检验知识体系的关键索引。从JVM内存模型与垃圾回收、并发编程的锁升级与AQS原理,到Spring IOC/AOP与循环依赖、MySQL B+树索引,再到Redis缓存异常与Kafka百万并发架构,这些核心考点共同构成大厂工程师的底层能力地图。掌握其原理,不仅能通过面试,更能指导线上性能优化与高并发系统设计。本文结合工程实践,梳理高频考点与高效备考方法,帮助读者将零散知识串联成完整体系。

weixin_32430941的博客 225

Java并发编程:synchronized

虽然多线程编程极大地提高了效率,但是也会带来一定的隐患。比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据。今天我们就来一起讨论下线程安全问题,以及Java中提供了什么机制来解决线程安全问题。   一.什么时候会出现线程安全问题?   在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一

可口可乐的博客 588

Qt Creator配置MSVC编译环境、调试环境

获取和安装:cdb.exe 在 Debugging tools for Windows 中,而现在已经不单独提供下载,只在Windows SDK 或者Windows Driver Kit里面包含 所以需要下载微软的调试工具,最少需要下载 Windows SDK。在windows上开发,一般使用Qt Creator自带mingw编译器,编译和调试都很方便,安装Qt时勾选后,自动配置完毕。但是有时候我们需要使用MSVC的编译器,这个时候我们没法直接使用,需要配置环境才能使用,以下是配置方案。

希望能帮助大家,希望大家多多支持,你们的支持是我前进的动力。 6986

[QT_055]设置QT源码调试(qtc+vs/mingw+msvc)

设置QT源码调试

kongcheng253的博客 3808

Qt —— QtCreator配置MSVC编译器调试环境(演示)

介绍 当Qt版本是MSVC时候貌似Creator中不可调试。本文只介绍Creator在Microsoft Visual C++ Compiler - CDB调试的配置。(亲测可行,调试方式快捷键等与Vs相同!)。 配置 1、首先下载Windows SDK调试工具,建议下载旧版(8.1,注意:Visual Studio不包含所需的调试工具,因此,必须单独安装它们。) 然后如图安装该工具: 2、在安...

6179

Synchronized你以为你真的懂?

synchronized是个啥 在多线程并发编程中synchronized一直是元老级角色,很多人都会称呼它为重量级锁。但是,随着Java SE 1.6对 synchronized进行了各种优化之后,有些情况下它就并不那么重了,Java SE 1.6中为了减少获得锁和释放锁带来的 性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程。 如果你对于synchronized的基础知识和用法...

lyztyycode的博客 3306

精心修炼Java并发编程(JUC)-volatile与synchronized关键字

volatile 是 JVM 提供的,中文意思是不稳定的,易变的,用volatile修饰变量是为了保证变量在多线程中的可见性,它表达的含义是:告诉编译器,对这个变量的读写,需要基于主内存保证多CPU的缓存一致性。保证变量对所有线程的可见性:当一条线程修改了 volatile 变量的值,新值对于其他线程来说是可以立即得知的。而普通变量不能做到这一点注意:1、volatile并不能保证并发操作的原子性,即不保证线程安全。

qq_46248151的博客 1166

【Java多线程】内置锁(Synchronized)的前世今生

**Java内置锁不需要显式的获取锁和释放锁,由JVM内部来实现锁的获取与释放。而且任何一个对象都能作为一把内置锁。在`JDK1.4`及之前就是使用`内置锁Synchronized`来进行线程同步控制的** `上文说,任何一个对象都能作为一把内置锁”,意味着synchronized关键字出现的地方,都有一个对象与之关联`,具体表现为: - 当synchronized作用于`普通方法`时,锁对象是`this`; - 当synchronized作用于`静态方法`时,锁对象是`当前类的Class对象`; -

墩墩分墩 3764

精心修炼Java并发编程(JUC)-线程三大特性

此处的变量(Variables)与 Java 编程中所说的变量有所区别,它包括了实例字段、静态字段和构成数值对象的元素,但不包括局部变量与方法参数,因为后者是线程私有的,不会被共享,自然就不会存在竞争问题。JMM 规定了。

qq_46248151的博客 1426

Java并发编程78讲:从JMM到线程池的系统学习路线

并发编程是Java后端开发绕不开的核心能力,尤其在高流量场景下,线程安全与性能优化直接决定系统稳定性。理解Java内存模型(JMM)与原子性、可见性、有序性三大特性,是掌握锁机制的前提。从synchronized的锁升级到AQS队列同步器,再到ConcurrentHashMap、线程池等JUC组件,构成了并发工具箱的完整图谱。这些原理广泛应用于秒杀扣减库存、异步任务处理、分布式缓存一致性等实际业务;与此同时,死锁排查、ThreadLocal内存泄漏、CAS的ABA问题也是面试高频考点。本文基于《Java并发

weixin_33507732的博客 260

Windows下用RKDevTool解包update.img的完整流程(含常见错误排查)

本文详细介绍了在Windows系统下使用RKDevTool工具解包update.img固件的完整流程。从工具准备、环境搭建到逐步解包操作,重点解析了输出文件结构,并提供了修改分区后重新打包的实战方法。文章还针对解包失败、打包错误等常见问题提供了详细的排查与解决方案,旨在帮助嵌入式开发者和爱好者高效完成固件分析与定制工作。

aa123的博客 716

JDK17线程性能优化:并发编程实战

JDK17线程性能优化指南:本文探讨了JDK17中多线程编程的关键优化策略,包括线程基础实现(Thread/Runnable)和三种常用线程池(FixedThreadPool、CachedThreadPool、SingleThreadExecutor)的配置与性能对比。通过实际代码示例展示了不同线程池在任务处理上的特点,并提供了性能测试方法。优化建议包括根据任务特性选择合适的线程池类型、合理设置线程数量,以及使用shutdown()方法正确管理线程池生命周期。这些实践能有效提升并发编程效率,同时规避死锁等常

fykam的博客 865

从零构建:ESP32S3无线DAP-Link调试器的硬件与软件协同设计

本文详细介绍了基于ESP32S3的无线DAP-Link调试器的硬件与软件协同设计,涵盖关键元器件选型、无线通信协议优化、双核任务处理及低功耗调试方案。通过ESP-NOW协议实现低延迟无线调试,结合USB协议栈与双核协同,为嵌入式开发提供高效灵活的调试解决方案。

gitlab7runner的博客 859

Java内存模型:理解并发编程的核心概念-优快云博客

19.9 元(AI 编程 / Agent 实战 | 本文同主题系统课程)·49.9 元(AI 时代成长方法论)。单篇不过瘾?订阅解锁全量源码、实战与答疑;文末附资料包领取方式 ↓Java内存模型:理解并发编程的核心概念在Java编程中,内存模型是一个至关重要的概念。它定义了Java程序在虚拟机中如何分配、使用和管理内存。特别是在多线程环境下,Java内存模型对于确保数据一致性和程序正确性具有重要意义。一、Java内存模型的演变在早期的Java版本中,内存模型是比较简化的。

求知若渴、虚心若愚 201

宝塔面板添加站点及运营商SSL免费证书的申请与使用

本文详细介绍在宝塔面板下如何正确添加站点、配置站点,及在运营商如何申请免费SSL证书,将其配置到宝塔站点里。 给你的网站加一把锁,教你免费SSL证书申请,配置到自己的网站中。 环境:Ubuntu server 18 工具:宝塔控制面板 平台:阿里云 Web服务器:Nginx 宝塔:https://www.bt.cn/ 阿里云:https://www.aliyun.com/ 腾讯云:https://cloud.tencent.com/ 要求: 1.宝塔控制面板搭建完毕。 2掌握域名解析的原理。 3.了解ht

贺兰山长老 5058

2026 具身智能 VLA/WAM 全栈学习与训练路线:从动作建模、SFT、RL 到世界模型的 RTX PRO 6000 96GB 单卡实战指南

我会把这张表做成“”而不是只列论文名:先核对每个模型截至的公开代码、训练目标和数据接口,再单独给你一套。对于没有公开、只有推理代码或训练细节不完整的模型,我会明确标出来,避免把“论文里提到”误写成“可复现”。目前有一个重要更新:。截至今天 NVIDIA 官方开源主线已经是换成 Cosmos-Reason2-2B/Qwen3-VL 架构,动作端仍是连续路线,而且官方明确给出单卡微调时默认只训练,峰值显存约。所以你的 96GB 单卡对 GR00T SFT 非常宽裕。可以。下面我按,把它整理成一张真正面向“

u013250861的博客 445

闭关修炼完阿里最新Java并发编程全优笔记,领导直接让我晋升到架构组了。

现在Java面试,问的是越来越底层。基本上规模大点的互联网公司都会对JVM,OS,算法,线程,IO等底层知识进行深入考察;其中粉丝反馈近期出去面试被问的最多,频次最高的技术栈当属多线程并发编程了。说到并发编程,很多人第一反应都是:难!难是肯定的,因为并发编程涉及到的知识面太广,你想要学懂并发编程,需要提前储备大量的底层知识,这样学习过程中理解起来才不会那么困难;才能在面试跟工作中轻松应对各种各样的并发场景问题。PS:并发编程还是所有Java初中级跨越至高开必须要迈过的一个槛。很多粉丝就经常跟我反馈,虽然意识

Java技术攻略的博客 128

PVE虚拟机运行不流畅,出现卡顿。

🏆本文收录于《优快云问答解惑-专业版》专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!

**My Coding Family** 3064

Java修炼之道--并发编程

原作地址:https://github.com/frank-lam/2019_campus_apply 前言 在本文将总结多线程并发编程中的常见面试题,主要核心线程生命周期、线程通信、并发包部分。主要分成 “并发编程” 和 “面试指南” 两 部分,在面试指南中将讨论并发相关面经。 参考资料: 《Java并发编程实战》 第一部分:并发编程 1. 线程状态转换 新建(New) 创建后尚未启动...

weixin_30312557的博客 384

MIKE21 教程 3.2 随空间变化的网格文件制作(.dfsu文件制作) 曼宁值网格文件制作

上一节讲解了如何制作时间序列文件。本节讲解如何制作仅随空间变化的文件。 目录 1 数据准备 1.1 基于地形设置 1.2 基于地形水深设置: 2 数据导入 3 数据导出

Hello Word 3941

x2-DFD:A framework for explainable and explainable deepfake detection

该文章提出了X2-DFD框架,用于可解释和可扩展的深度伪造检测。通过评估大型多模态语言模型(MLLMs)在深度伪造检测中的内在能力,发现它们在不同伪造相关特征上的有效性各异。基于此,实施了针对性的微调策略,显著提高了MLLMs的解释能力,并整合了外部深度伪造检测器(EDDs),以结合MLLMs和传统检测器的互补优势,实现更好的检测和解释。

志在山顶的人,不会贪念山腰的风景 510

Java核心基础:从JVM内存模型到并发编程的实战内功修炼

Java作为企业级应用的主流语言,其核心基础是构建稳健、高性能系统的基石。理解Java内存模型(JMM)是掌握并发编程的关键,它定义了线程间共享变量的访问规则,解决了可见性、原子性和有序性问题。通过深入分析JVM内存区域划分,如堆、方法区(Metaspace),开发者能够有效诊断和解决`OutOfMemoryError`等线上疑难杂症,这是性能调优与系统稳定的技术保障。在并发场景下,从`synchronized`锁的升级优化机制到`java.util.concurrent`工具包的无锁CAS实现,这些原理为

weixin_30399155的博客 395

uniapp [微信小程序] - 详解微信小程序端接入mqtt全流程及示例源码教程,uniApp微信小程序端如何接入MQTT插件并实现连接/断开+发布订阅+消息队列+离线重连等各种示例代码(开箱即用)

uniapp微信小程序接入MQTT,uniapp中使用mqtt,uniapp 微信小程序端接入mqtt示例代码教程,适用于微信小程序的MQTT插件,uniapp开发微信小程序连接mqtt,uniApp微信小程序接入MQTT协议,uniapp vue3微信小程序使用MQTT的小例子接入流程,使用uniapp开发小程序接入mqtt,如何在uniapp项目中使用mqtt,uniapp mqtt的使用示例,mqtt连接报错,如何在项目里集成mqtt,Mqtt协议,主动连接mqtt,订阅消息,实现发布,订阅和接收消息

🏆 前端领域优质创作者 2882

Java进阶必读:从JVM到OOM排查的完整修炼

Java开发者在掌握基础语法后,常会面临程序变慢、内存异常、类库冲突等工程难题,这些问题的根源往往藏在JVM的内存管理与类加载机制中。理解JDK、JRE、JVM的边界与运行时数据区,是提升代码诊断能力的第一步;而深入集合类源码,如HashMap的扩容与红黑树转换,能帮助开发者写出更高效的数据处理逻辑。并发编程则是区分初中级开发者的分水岭,正确配置线程池参数比“new Thread”更能应对高并发场景。当系统出现“java: outofmemoryerror: insufficient memory”时,基于

weixin_27697385的博客 219

OV9734 DATASHEET,完整规格书

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

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

并发编程修炼五:lock

一.synchronized的缺陷 二.java.util.concurrent.locks包下常用的类 三.锁的相关概念介绍

yjgithub的博客 226

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

多线程之初级修炼

定时器是何物客户端发出请求之后,要等待服务器的响应,如果超出定时器所规定的时间,还没有响应,就不再等待,继续执行。

2502_91207865的博客 479

IMSL CNL 7.0.0 (x86-64)Crack

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

上一篇: 并发编程修炼三:Thread类的使用
下一篇: 并发编程修炼五:lock
yjgithub
博客等级 码龄11年 53粉丝 105原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值