美团-从ReentrantLock的实现看AQS的原理及应用

OpenIddict实战:5步搞定.NET Core应用的OIDC单点登录集成 本文详细介绍了如何使用OpenIddict在.NET Core应用中快速实现OIDC单点登录集成。通过五个关键步骤,包括环境准备、数据库集成、客户端注册、令牌配置和多应用SSO集成,帮助开发者高效构建安全的企业级单点登录解决方案。 阅读详情

原文见https://mp.weixin.qq.com/s/sA01gxC4EbgypCsQt5pVog,本文章做了大量删减。


前言

Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。
在这里插入图片描述


1 ReentrantLock

1.1 ReentrantLock特性概览

ReentrantLock意思为可重入锁,指的是一个线程能够对一个临界资源重复加锁。
在这里插入图片描述

// **************************Synchronized的使用方式**************************
// 1.用于代码块
synchronized (this) {}
// 2.用于对象
synchronized (object) {}
// 3.用于方法
public synchronized void test () {}
// 4.可重入
for (int i = 0; i < 100; i++) {
    synchronized (this) {}
}
// **************************ReentrantLock的使用方式**************************
public void test () throw Exception {
    // 1.初始化选择公平锁、非公平锁
    ReentrantLock lock = new ReentrantLock(true);
    // 2.可用于代码块
    lock.lock();
    try {
        try {
            // 3.支持多种加锁方式,比较灵活; 具有可重入特性
            if(lock.tryLock(100, TimeUnit.MILLISECONDS)){ }
        } finally {
            // 4.手动释放锁
            lock.unlock()
        }
    } finally {
        lock.unlock();
    }
}

1.2 ReentrantLock与AQS的关联

ReentrantLock支持公平锁和非公平锁,并且ReentrantLock的底层就是由AQS来实现的。

// java.util.concurrent.locks.ReentrantLock#NonfairSync

// 非公平锁
static final class NonfairSync extends Sync {
    ...
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
        }
  ...
}
  • 若通过CAS设置变量State(同步状态)成功,也就是获取锁成功,则将当前线程设置为独占线程。
  • 若通过CAS设置变量State(同步状态)失败,也就是获取锁失败,则进入Acquire方法进行后续处理。
// java.util.concurrent.locks.ReentrantLock#FairSync

static final class FairSync extends Sync {
  ...  
    final void lock() {
        acquire(1);
    }
  ...
}

结合公平锁和非公平锁的加锁流程,虽然流程上有一定的不同,但是都调用了Acquire方法,而Acquire方法是FairSync和UnfairSync的父类AQS中的核心方法。

2 AQS

首先,我们通过下面的架构图来整体了解一下AQS框架:

在这里插入图片描述

上图中有颜色的为Method,无颜色的为Attribution。
总的来说,AQS框架共分为五层,自上而下由浅入深,从AQS对外暴露的API到底层基础数据。
当有自定义同步器接入时,只需重写第一层所需要的部分方法即可,不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时,先经过第一层的API进入AQS内部方法,然后经过第二层进行锁的获取,接着对于获取锁失败的流程,进入第三层和第四层的等待队列处理,而这些处理方式均依赖于第五层的基础数据提供层。

2.1 原理概览

AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中。

CLH:Craig、Landin and Hagersten队列,是单向链表,AQS中的队列是CLH变体的虚拟双向队列(FIFO),AQS是通过将每条请求共享资源的线程封装成一个节点来实现锁的分配。
在这里插入图片描述
AQS使用一个Volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作,通过CAS完成对State值的修改。

2.1.1 AQS数据结构

AQS中最基本的数据结构——Node,Node即为上面CLH变体队列中的节点。
在这里插入图片描述

解释一下几个方法和属性值的含义:
方法和属性值
waitStatus当前节点在队列中的状态
prev前驱指针
next后继指针
thread表示处于该节点的线程
nextWaiter指向下一个处于CONDITION状态的节点(由于本篇文章不讲述Condition Queue队列,这个指针不多介绍)
predecessor返回前驱节点,没有的话抛出npe

线程两种锁的模式: SHARED表示线程以共享的模式等待锁
EXCLUSIVE表示线程正在以独占的方式等待锁

waitStatus有下面几个枚举值:
CANCELLED为1,表示线程获取锁的请求已经取消了
SIGNAL为-1,表示线程已经准备好了,就等资源释放了
CONDITION为-2,表示节点在等待队列中,节点线程等待唤醒
PROPAGATE为-3,当前线程处在SHARED情况下,该字段才会使用
0当一个Node被初始化的时候的默认值

2.1.2 同步状态State

AQS中维护了一个名为state的字段,意为同步状态,是由Volatile修饰的,用于展示当前临界资源的获锁情况。

// java.util.concurrent.locks.AbstractQueuedSynchronizer
private volatile int state;
方法名描述
protected final int getState()获取State的值
protected final void setState(int newState)设置State的值
protected final boolean compareAndSetState(int expect, int update)使用CAS方式更新State

这几个方法都是Final修饰的,说明子类中无法重写它们。我们可以通过修改State字段表示的同步状态来实现多线程的独占模式和共享模式(加锁过程)。

对于我们自定义的同步工具,需要自定义获取同步状态和释放状态的方式,也就是AQS架构图中的第一层:API层。

2.2 AQS重要方法与ReentrantLock的关联

AQS提供了大量用于自定义同步器实现的Protected方法。自定义同步器实现的相关方法也只是为了通过修改State字段来实现多线程的独占模式或者共享模式。
一般来说,自定义同步器要么是独占方式,要么是共享方式,它们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。ReentrantLock是独占锁,所以实现了tryAcquire-tryRelease。

加锁

  1. 通过ReentrantLock的加锁方法Lock进行加锁操作。
  2. 会调用到内部类Sync的Lock方法,由于Sync#lock是抽象方法,根据ReentrantLock初始化选择的公平锁和非公平锁,执行相关内部类的Lock方法,本质上都会执行AQS的Acquire方法。
  3. AQS的Acquire方法会执行tryAcquire方法,但是由于tryAcquire需要自定义同步器实现,因此执行了ReentrantLock中的tryAcquire方法,由于ReentrantLock是通过公平锁和非公平锁内部类实现的tryAcquire方法,因此会根据锁类型不同,执行不同的tryAcquire。
  4. tryAcquire是获取锁逻辑,获取失败后,会执行框架AQS的后续逻辑,跟ReentrantLock自定义同步器无关。

解锁

  1. 通过ReentrantLock的解锁方法Unlock进行解锁。
  2. Unlock会调用内部类Sync的Release方法,该方法继承于AQS。
  3. Release中会调用tryRelease方法,tryRelease需要自定义同步器实现,tryRelease只在ReentrantLock中的Sync实现,因此可以看出,释放锁的过程,并不区分是否为公平锁。
  4. 释放成功后,所有处理由AQS框架完成,与自定义同步器无关。

在这里插入图片描述

2.3 通过ReentrantLock理解AQS

2.3.1 线程加入等待队列

// java.util.concurrent.locks.AbstractQueuedSynchronizer

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
2.3.1.1 加入队列的时机

当执行Acquire(1)时,会通过tryAcquire获取锁。在这种情况下,如果获取锁失败,就会调用addWaiter加入到等待队列中去。

2.3.1.2 如何加入队列

获取锁失败后,会执行addWaiter(Node.EXCLUSIVE)加入等待队列

  • 把对应的线程以Node的数据结构形式加入到双端队列里,返回的是一个包含该线程的Node。而这个Node会作为参数,进入到acquireQueued方法中。acquireQueued方法可以对排队中的线程进行“获锁”操作。

总的来说,一个线程获取锁失败了,被放入等待队列,acquireQueued会把放入队列中的线程不断去获取锁,直到获取成功或者不再需要获取(中断)。

2.3.1.3 等待队列中线程出队列时机

前置节点是头结点,且当前线程获取锁成功

2.3.2 CANCELLED状态节点生成

通过cancelAcquire方法,将Node的状态标记为CANCELLED。

2.3.3 如何解锁

ReentrantLock在解锁的时候,并不区分公平锁和非公平锁。中断恢复后的执行流程

2.3.4 小结

Q:某个线程获取锁失败的后续流程是什么呢?
A:存在某种排队等候机制,线程继续等待,仍然保留获取锁的可能,获取锁流程仍在继续。

Q:既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?
A:是CLH变体的FIFO双端队列。

Q:处于排队等候机制中的线程,什么时候可以有机会获取锁呢?
A:可以详细看下2.3.1.3小节。

Q:如果处于排队等候机制中的线程一直无法获取锁,需要一直等待么?还是有别的策略来解决这一问题?
A:线程所在节点的状态会变成取消状态,取消状态的节点会从队列中释放,具体可见2.3.2小节。

Q:Lock函数通过Acquire方法进行加锁,但是具体是如何加锁的呢?
A:AQS的Acquire会调用tryAcquire方法,tryAcquire由各个自定义同步器实现,通过tryAcquire完成加锁过程。

3 AQS应用

3.1 ReentrantLock的可重入应用

ReentrantLock的可重入性是AQS很好的应用之一,在了解完上述知识点以后,我们很容易得知ReentrantLock实现可重入的方法。不管是公平锁还是非公平锁,都有一段逻辑。有一个同步状态State来控制整体可重入的情况。State是Volatile修饰的,用于保证一定的可见性和有序性。
(1) State初始化的时候为0,表示没有任何线程持有锁。
(2) 当有线程持有该锁时,值就会在原来的基础上+1,同一个线程多次获得锁是,就会多次+1,这里就是可重入的概念。
(3) 解锁也是对这个字段-1,一直到0,此线程对锁释放。

3.2 JUC中的应用场景

同步工具同步工具与AQS的关联
ReentrantLock使用AQS保存锁重复持有的次数。当一个线程获取锁时,ReentrantLock记录当前获得锁的线程标识,用于检测是否重复获取,以及错误线程试图解锁操作时异常情况的处理。
ReentrantReadWriteLock使用AQS同步状态中的16位保存写锁持有的次数,剩下的16位用于保存读锁的持有次数。
Semaphore使用AQS同步状态来保存信号量的当前计数。tryRelease会增加计数,acquireShared会减少计数。
CountDownLatch使用AQS同步状态来表示计数。计数为0时,所有的Acquire操作(CountDownLatch的await方法)才可以通过。
ThreadPoolExecutorWorker利用AQS同步状态实现对独占线程变量的设置(tryAcquire和tryRelease)。

3.3 自定义同步工具

public class LeeLock  {

    private static class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire (int arg) {
            return compareAndSetState(0, 1);
        }

        @Override
        protected boolean tryRelease (int arg) {
            setState(0);
            return true;
        }

        @Override
        protected boolean isHeldExclusively () {
            return getState() == 1;
        }
    }

    private Sync sync = new Sync();

    public void lock () {
        sync.acquire(1);
    }

    public void unlock () {
        sync.release(1);
    }
}
public class LeeMain {
    static int count = 0;
    static LeeLock leeLock = new LeeLock();

    public static void main (String[] args) throws InterruptedException {

        Runnable runnable = () -> {
            try {
                leeLock.lock();
                for (int i = 0; i < 10000; i++) {
                    count++;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                leeLock.unlock();
            }

        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
        // 线程没有执行完之前,会一直阻塞在join方法处。
        thread1.join();
        thread2.join();
        System.out.println(count);
    }

}

上述代码每次运行结果都会是20000。通过简单的几行代码就能实现同步功能,这就是AQS的强大之处。

分享Charles抓包 显示<unknown> 解决方案 ! 首先电脑上需要安装charles,然后需要设置手机上的WiFi设置,修改配置中的代理设置,把服务器的主机名设置成你电脑当前的ip地址(需要注意的是,你的代理服务器主机名的ip地址要和你当前连的ip地址的网段需要一致)。像 Charles 这样的工具,虽然功能强大,但仍然需要用户具备一定的配置和排障能力,以应对各种复杂的网络环境。通过将 Charles 的 SSL 证书正确安装到移动设备中,并在设置中启用“允许不安全的内容”选项,最终成功解决了这一问题,并准确抓取到所有请求数据。Charles 显示。 阅读详情

相关推荐

汽车级智能栅极驱动器深度解析 | 连载01:DRV871x-Q1系列产品架构与核心特性

集成度:单芯片集成多通道驱动器、电源管理、电流检测智能化:自适应控制算法,减少外部元件可靠性:多重保护机制,满足汽车级要求灵活性:SPI和硬件两种接口,适应不同应用需求。

VehSwHwDeveloper的博客 1151

美团技术团队--从ReentrantLock的实现看AQS的原理及应用

前言 Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。本文会从应用层逐渐深入到原理层,并通过ReentrantLock的基本特性和ReentrantLock与AQS的关联,来深入解读AQS相关独占锁...

mhHao的博客 823

基于FPGA的ov5640摄像头简易图像采集装置详解

本文介绍一种基于FPGA的,通过流模式DDR实现OV5640摄像头数据采集并显示的方法。

qq_42230252的博客 1万+

Java-ReentrantLock-AQS-原理-应用-美团技术团队

Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。本文会从应用层逐渐深入到原理层,并通过ReentrantLock的基本特性和ReentrantLock与AQS的关联,来深入解读AQS相关独占锁的知识点,同时采取问答的模式来帮助大家理解AQS。

Java程序员的知识博客 1281

PADS导出PCB正反面丝印为CAD文件的方法

本文介绍如何将PADS绘制的pcb格式的PCB文件顶底层丝印及板框导出为CAD的dxf文件格式,并且将底层丝印进行镜像为正常视角

luguodeyo的博客 3292

Zephyr、FreeRTOS 和 RT-Thread

你是…更适合用嵌入式新手,刚接触 RTOSFreeRTOS(入门简单)IoT 项目开发,需要 BLE/WiFi/MQTTZephyr(协议全)想快速构建可部署的产品Zephyr(模块统一)只做单个任务或简单状态机控制FreeRTOS(轻量)做平台化、持续集成、自动测试Zephyr(CI 支持强)

is0815的博客 2241

从ReentrantLock的实现看AQS的原理及应用(转自美团技术团队)

前言 Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。本文会从应用层逐渐深入到原理层,并通过ReentrantLock的基本特性和ReentrantLock与AQS的关联,来深入解读AQS相关独占锁的知识点,同时采取问答的模式来帮助大家理解AQS。由于篇幅原因,本篇文章主要阐述AQS中独占锁的逻辑和Sync

CrazyTiger的博客 519

移动硬盘报错0x80070570?5分钟搞定CHKDSK修复(附详细参数说明)

本文针对移动硬盘报错0x80070570问题,提供了详细的CHKDSK修复方法。通过5分钟核心修复流程,指导用户使用系统自带工具快速修复文件系统逻辑错误,恢复硬盘访问。文章还深入解析了CHKDSK参数,并提供了备选数据恢复方案与日常防护建议。

app77的博客 234

【美团一面】AQS是什么,详细讲解一下AQS

AQS(AbstractQueuedSynchronizer)是Java并发包的核心框架,为ReentrantLock、Semaphore等同步工具提供底层支持。它通过volatile state变量管理同步状态,使用CLH变种队列处理线程竞争,支持独占和共享两种模式,并实现了Condition条件变量机制。AQS采用模板方法模式,子类只需重写tryAcquire等关键方法即可实现自定义锁逻辑。其优点包括高效性(基于CAS)、灵活性和可扩展性,但实现复杂度较高。该框架通过状态管理、队列调度和原子操作,为Ja

Hzx895554135的博客 1014

Tailscale 自建 DERP 中继服务器

Tailscale自建DERP中继服务器

Accompagner的博客 4325

高通camera驱动 camx read xml 和 probe 流程 和 sensor I2C读写的波形图 (三)

Camx 刚接触的时候对于这个xml 一脸懵逼,实际上和mm-camera差不多就是以xml node节点的方式标准化了driver相关的code,exposure 和 gain的逻辑实现还是在同级目录的cpp中,并且将 xml 的解析工作放在了 out/target/<product>/gen/STATIC_LIBERARIES/libcamxgenerated_intermediates/generated/ 中主要是包含了: g_chromatix : tuning 相关xml的解析co

shangbolei的博客 1万+

AQS理解和认识

详情请看大佬的说明 https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html

Jornici的博客 265

Unreal5从入门到精通之如何在VR中使用3DUI

前面我们讲过《Unreal5从入门到精通之UMG的使用》一般制作游戏时,UI都是添加到屏幕视窗上面的,但是在制作VR时,我们往往需要制作一个3D的UI,放置在场景中,然后使用手柄的射线来和UI进行交互。今天我们就来讲解下,如何在VR中创作和交互3DUI首次操作会感觉好复杂,多多练习,当你熟悉了UE蓝图的操作后会发现,这只是一个常规的蓝图操作而已。祝大家在虚幻的学习过程中非常顺利。希望这篇博客对你有所帮助。

TxNet 1984

美团大佬带你从ReentrantLock的实现看AQS的原理及应用!

Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。本文会从应用层逐渐深入到原理层,并通过ReentrantLock的基本特性和ReentrantLock与AQS的关联,来深入解读AQS相关独占锁的知识点,同时采取问答的模式来帮助大家理解AQS。

mf97532的博客 272

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

从ReentrantLock的实现看AQS的原理及应用

总第374篇2019年 第52篇AQS作为JUC中构建锁或者其他同步组件的基础框架,应用范围十分广泛,这篇文章会带着大家从可重入锁一点点揭开AQS的神秘面纱。前言Java中的大部分同步类...

美团技术团队 8484

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

阿里十年 “帕鲁” 手把手带你从ReentrantLock的实现看AQS的原理及应用

了解 AQS 基本原理以后,按照上面所说的 AQS 知识点,自己实现一个同步工具。@Override@Override@Override通过我们自己定义的 Lock 完成一定的同步功能。上述代码每次运行结果都会是 20000。通过简单的几行代码就能实现同步功能,这就是 AQS 的强大之处。我们日常开发中使用并发的场景太多,但是对并发内部的基本框架原理了解的人却不多。

zxyhj的博客 1783

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

【Java核心能力】上万字 ReentrantLock 原理全面详解!

本来写这篇文章是因为自己对 ReentrantLock 的了解一直都比较表面,原理了解的还不是那么的清楚,并且对 AQS 也都是一知半解,但是在面试中这些都是比较常问的内容,如果原理了解比较清楚的话,对面试会有很大的帮助,因此自己在这里也总结了一份,本来以为很快就写完了,没想到竟然写了两天,文字的话就有 6000 +,ReentrantLock 中大部分的内容也都说到了,并且配有许多图片,如果感觉内容太多的话,可以对 ReentrantLock 中加锁以及解锁内容重点阅读,希望对你有所帮助!

qq_45260619的博客 2079

reentrantlock原理_从ReentrantLock的实现看AQS的原理及应用

点击上方蓝字关注我!本文转载自美团技术团队【订阅号ID:meituantech】,经平台同意授权转载。AQS作为JUC中构建锁或者其他同步组件的基础框架,应用范围十分广泛,这篇文章会带着大家从可重入锁一点点揭开AQS的神秘面纱。前言Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为A...

weixin_39778393的博客 117

并发专栏-队列同步器 AQS 以及 Reentrantlock 应用

两个都是native本地方法。文章中反复提到过,使用多线程很重要的考量点是线程切换的开销,想象一下,如果采用非公平锁,当一个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻再次获取同步状态的几率就变得非常大,所以就减少了线程的开销。这段代码被执行的条件是 failed 为 true,正常情况下,如果跳出循环,failed 的值为 false,如果不能跳出循环貌似怎么也不能执行到这里,所以只有不正常的情况才会执行到这里,也就是会发生异常,才会执行到此处。

Dc253061007的博客 869

多线程8:从ReentrantLock的实现看AQS的原理及应用

了解 AQS 基本原理以后,按照上面所说的 AQS 知识点,自己实现一个同步工具。} }通过我们自己定义的 Lock 完成一定的同步功能。} } };} }上述代码每次运行结果都会是 20000。通过简单的几行代码就能实现同步功能,这就是 AQS 的强大之处。我们日常开发中使用并发的场景太多,但是对并发内部的基本框架原理了解的人却不多。

Java开发工程师,专注微服务、分布式系统,部署运维,分享实战经验和源码解析。 182

AQS——从ReentrantLock入手,了解锁的获取的释放

AQS 以下内容可参考美团技术团队:从ReentrantLock的实现看AQS的原理及应用 讲到ReentrantLock就不得不讲AQS,因为Lock的底层就是基于AQS来实现的。那么。什么时AQS呢? AQS全称AbstractQueuedSynchronizer,是JUC中的一个类。它提供了原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。常见的ReentranrLock、Semaphore、CountDownLatch、ThreadPoolExecutor中都是用到了这个类。 AQS类源

wuraox的博客 578

线程协作工具类原理(2):AQS

AQS(并发灵魂人物,进阶必备)

底层码农 6804

【JavaEE】AQS原理

首先有个整体认识,全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架。常用的ReentrantLock、Semaphore、CountDownLatch等都有实现它。

p_fly的博客 1137
上一篇: 美团Java线程池实现原理与常见问题
下一篇: shell脚本定时同步远程数据库数据
想要追着光
博客等级 码龄9年 84粉丝 53原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值