AQS(AbstractQueuedSynchronizer)抽象队列同步器

Gemini永久会员 在Java面试中,关于AQS(AbstractQueuedSynchronizer,抽象队列同步器) 在Java面试中,关于AQS(AbstractQueuedSynchronizer,抽象队列同步器)的考察点主要集中在核心机制、实现原理、应用场景以及与具体同步组件的结合上。 阅读详情

前言

AQS 绝对是JUC的重要基石,也是面试中经常被问到的,所以我们要搞清楚这个AQS到底是什么?骑工作原理是什么?

AQS是什么?

是用来构建锁或者其他同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO对列来完成资源获取线程的排队工作,并通过一个int类型变量表示持有锁的状态。
在这里插入图片描述
CLH队列:CLH(Craig, Landin, and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。通过CAS完成对State值的修改。
同步对列的内部结构及继承关系
在这里插入图片描述
在这里插入图片描述

用银行办理业务的案例模拟AQS如何进行线程管理和通知机制

1、初始化的时候,state = 0 (0 表示没有人,1表示有人),线程池也有没有人在执行,现在就是没有顾客的时候,因此第一个线程去的时候都是公平锁状态直接到窗口办理业务。
在这里插入图片描述
2、现在有一个线程Thread A进来那么就直接到了业务窗口去办理,并且通过CAS将state的值变成1。
在这里插入图片描述
3、此时有一个线程Thread B 进来,通过getStatue() 方法查看到state = 1,此时ThreadA有在占用着,所以现在ThreadB线程就必须先入队等待ThreadA结束后再调用,但是由于现在队列是空的,所以ThreadB线程并不会马上进入到队列,他会先进入addWaiter() 方法到enq()这个方法中去,这个方法实质上就是一个自旋锁,在这个方法中主要的时候先要实现一个队列的初始化工作,先形成一个傀儡结点(哨兵结点)起到一个占位的作用,然后才能将ThreadB线程挂在后面。
在这里插入图片描述
部分源码附上:

	private Node addWaiter(Node mode) {
     Node node = new Node(Thread.currentThread(), mode);
     // Try the fast path of enq; backup to full enq on failure
     Node pred = tail;// 将tail的前指针赋值过去
     if (pred != null) { //一开始的时候并没有指向所以位null,因此条件不成立不走里面的语句
         node.prev = pred;
         if (compareAndSetTail(pred, node)) {//CAS
             pred.next = node;
             return node;
         }
     }
     enq(node);//一开始会来走这个enq()这个方法。
     return node;
 }

因为当前的ThreadB线程进入时,tail的prev指针为null所以就会进enq()方法。如下:

	private Node enq(final Node node) {
     for (;;) {
         Node t = tail;
         if (t == null) { // Must initialize
             if (compareAndSetHead(new Node()))
                 tail = head;
         } else {
             node.prev = t;
             if (compareAndSetTail(t, node)) {
                 t.next = node;
                 return t;
             }
         }
     }
 }

4、ThreadC线程开始入队,那么这个就是跟ThreadB是一样的,只不过是说没有了初始化那步了,直接挂在线程ThreadB上即可。
在这里插入图片描述
5、ThreadB 会去尝试acquireQueued()这个方法,那么第一次的时候会将哨兵结点的waitStatus = -1; 然后继续自旋,进入到if条件的下一个条件中被调用park() 阻塞掉,等待着ThreadA 线程的unpark()。这样才算真正的入列等待了。
在这里插入图片描述

	final boolean acquireQueued(final Node node, int arg) {
     boolean failed = true;
     try {
         boolean interrupted = false;
         for (;;) {
             final Node p = node.predecessor();
             if (p == head && tryAcquire(arg)) {
                 setHead(node);
                 p.next = null; // help GC
                 failed = false;
                 return interrupted;
             }
             if (shouldParkAfterFailedAcquire(p, node) &&
                 parkAndCheckInterrupt()) //第一次的时候会阻塞在前面的条件并将哨兵的waitState置为-1,第二次的话第一个条件为真,所以会走第二个判断条件,会将访问的线程给阻塞掉等待unpark();
                 interrupted = true;
         }
     } finally {
         if (failed)
             cancelAcquire(node);
     }
 }
	private final boolean parkAndCheckInterrupt() {
     LockSupport.park(this); //将访问次方法的线程给阻塞掉,等待unpark()方法唤醒
     return Thread.interrupted();
 }

6、当ThreadA线程办好业务的时候那么就会调用unlock()方法释放锁,unlock() 方法中又会调用sync.release()方法,并将当前窗口的线程变成null,state 置成0,通过调用 unparkSuccessor()方法将 傀儡结点的 waitState = 0。
在这里插入图片描述

	private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread); //释放锁唤醒等待队列中的等待线程
    }

7、接着会调用unpark()方法将在前面等待的ThreadB线程给唤醒去窗口办业务。将head结点指向ThreadB结点,ThreadB的prev结点为null,next结点也为null,ThreadB变成空结点,此节点就成了新的哨兵结点,原来的哨兵结点被GC回收。
在这里插入图片描述
8、ThreadC线程出列和上面的过程是一样的。

结语

AQS的核心大致是这样,如果说我讲的你还是没有听懂的话,可以去B站看善硅谷阳哥的高频面试题那集去看看。我只是将其做了一个简化,用自己的话把这个过程复述出来,希望能对你有所帮助。
收工~

【编译原理】LL(1)分析方法 生成语法树(c++实现) 基本流程 输入文法->消除左递归->提取左公共因子->求FIRST集->求FOLLOW集->LL(1)分析表->表驱动的预测语法分析过程->输出语法分析结果和语法树 阅读详情

相关推荐

代谢组学数据分析实战:用R语言从PCA、PLS-DA到OPLS-DA一步步搞定(附代码)

本文详细介绍了使用R语言进行代谢组学数据分析的全流程,从数据预处理到PCA、PLS-DA和OPLS-DA模型构建与验证。通过实际案例和代码示例,帮助研究者掌握关键分析技术,提升数据解读能力,特别适合生命科学领域的研究人员。

weixin_28689507的博客 133

AQS(抽象队列同步器)

一、什么是 AQS ? AQS即AbstractQueuedSynchronizer的缩写,是并发编程中实现同步器的一个框架。 AQS基于一个FIFO双向队列实现,被设计给那些依赖一个代表状态的原子int值的同步器使用。我们都知道,既然叫同步器,那个肯定有个代表同步状态(临界资源)的东西,在AQS中即为一个叫state的int值,该值通过CAS进行原子修改。 在AQS中存在一个...

青鱼入云的博客 2162

双有源桥变换器(DAB)TPS控制:从理论建模到Simulink/PSIM仿真实践

本文深入探讨了双有源桥变换器(DAB)的三重移相(TPS)控制技术,从理论建模到仿真实现。文章详细分析了TPS相比SPS和DPS的优势,阐述了其12种工况的划分逻辑与电流应力最优调制策略,并提供了在Simulink和PSIM平台搭建仿真模型的详细步骤与实战经验,帮助工程师掌握这一高效控制方法。

white的博客 584

抽象队列同步器AQS(AbstractQueuedSynchronizer)

AQS(AbstractQueuedSynchronizer)

张矜持的博客 1482

ACPWorkbench_for_BP10

ADC支持9种采样率:8000Hz、11025Hz、12000Hz、16000Hz、22050Hz、24000Hz、32000Hz、44100Hz 以及 48000Hz。选择5种MCLK源时, 模块内部的MCLK源无效。I2S 采样率支持:8K,11.025K,12K,16K, 22.05K, 24K, 32K, 44.1K, 48K, 88.2K, 96K, 176.4K 和 192K。DAC采样率支持:8K,11.025K,12K,16K,22.05K, 24K, 32K, 44.1K和48K。

dddds222_的博客 4549

Qi标准下的充电协议及其记忆技巧

记忆无线充电协议中的。

m0_73991989的博客 1319

AQS 抽象队列同步器

ReentrantLock, Semaphore, CountDownLatch 等。 AQS 的核心思想是基于一个同步状态变量来管理线程之间的共享资源访问。这个状态变量是一个整数,通过内部队列(CLH lock 队列的一种变种)来协调多个线程对这个状态的访问。当一个线程请求访问被另一个线程占用的状态时,它会被放入队列中等待

junxinsiwo的专栏 1392

【WRF理论第十九期】内陆湖泊、水体的处理方式

【WRF理论第十九期】内陆湖泊、水体的处理方式

WW、forever的博客 358

抽象队列同步器(AQS)

抽象队列同步器(AQS)

superstar66的博客 180

青少年C/C++软件编程(四级)等级考试真题及答案解析目录

3. 带通配符的字符串匹配。1. 怪盗基德的滑翔翼。3.最长公共子上升序列。4.宠物小精灵之收服。

长春高老师编程 3028

无人机——电机篇(一)

1. 电机的定义 电机俗称“马达”,是无人机的动力来源,无人机通过改变电机的转速来改变无人机的飞行状态。即改变每个电机的速度,使得无人机能够盘旋空中,上升或下降,或向各个方向移动。2. 电机的分类 电机分为:“无刷电机”和“有刷电机” - 无刷电机:是采用半导体开关器件来实现电子换向,具有可靠性高、低干扰、低噪声低、寿命长等优点。一般应用在稍大型的飞行器,载重大,可以有更广泛的用途。 - 有刷电机:是内含电刷装置的将电能转换成机械能(电动机)或将机械能转换成电能(发电机)的旋转电机。有刷电机是所有电机的基

weixin_46066938的博客 1万+

AQS(abstractqueuedSynchronizer)抽象队列同步器详解

AQS(abstractqueuedSynchronizer)抽象队列同步器,基于模板方法模式,是用来构建锁或者其他同步组件的基础框架。它使用int成员变量(state)来表示同步状态,通过内置的FIFO队列(CLH队列)来完成资源获取线程的排队工作。

weixin_43896829的博客 592

渗透测试工具之Sliver

攻击者需要将生成的植入物传输到目标系统,并执行它。C2 框架的架构与 Metasploit(如 Meterpreter)类似,但更专注于。(Red Teaming)中常见的攻击工具之一,攻击者利用 C2 服务器。组成,二者共同实现 C2 服务器与受控设备的通信。植入物(Implants)是 Sliver 生成的。抓包分析时,可以看到所有数据都是加密传输的。和**监听器(Listeners)**功能。Sliver 支持多种通信协议,如。随着安全防御的不断提升,企业应加强。进行通信,数据仍然是加密的。

idlecloud0105的博客 1934

AQS(AbstractQueuedSynchronizer)抽象队列同步器

AQS(AbstractQueuedSynchronizer)是构建锁和同步器的框架

花开的博客 963

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

pscad IEEE标准系统搭建说明

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

【AQS】- 读源码全面解析AbstractQueuedSynchronizer抽象队列同步器

AQS(AbstractQueuedSynchronizer)是Java并发包中的核心同步框架,用于构建锁和同步组件(如ReentrantLock、Semaphore等)。它通过FIFO等待队列和volatile修饰的state变量实现排他锁和共享锁机制。AQS使用双向链表实现同步队列,支持高效的中断处理、线程挂起和条件队列操作。同步队列用于锁的获取和释放,而条件队列允许线程在特定条件下挂起和唤醒。双向链表的优势在于支持高效的前驱节点访问和队列遍历,减少线程竞争,提升并发性能。

1+1=王的博客 850

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

抽象队列同步器 AbstractQueuedSynchronizer -- AQS

抽象队列同步器 AbstractQueuedSynchronizer 源码 独占模式/共享模式 Condition 接口

米二的博客 758

【JUC】AQS(AbstractQueuedSynchronizer, 抽象队列同步器)

本文档概述了AQS(Abstract Queued Synchronizer)的基础理论知识及其在Java并发控制中的核心地位。AQS作为Java并发库(JUC)的基石,通过一个抽象的FIFO队列和一个表示同步状态的int变量,为锁和其他同步组件提供了一个通用的实现框架。文档解释了公平锁与非公平锁的区别、可重入锁的概念、自旋思想以及LockSupport的作用,并介绍了AQS内部的CLH队列变体和关键数据结构——Node节点,深入浅出地剖析了AQS的工作原理。

我只管埋头前行,剩下的交给时间。 1523

AQS介绍(AbstractQueuedSynchronizer 抽象队列同步器)

AQS 是 Java 并发包的核心基础框架,为构建锁和同步器提供了底层支持。理解 AQS 是掌握 Java 并发编程的关键。// 其他方法实现...AQS 通过精妙的设计,将同步器的通用逻辑抽象出来,让开发者只需关注特定同步策略的实现。理解 AQS 的工作机制,不仅能帮助更好地使用 JUC 中的工具类,也能为设计自定义同步组件提供坚实基础。

gwndjsh的博客 1407

JUC解析 抽象队列同步器(AQS-AbstractQueuedSynchronizer)

抽象队列同步器(AQS-AbstractQueuedSynchronizer) 从名字上来理解: 抽象:是抽象类,具体由子类实现 队列:数据结构是队列,使用队列存储数据 同步:基于它可以实现同步功能 我们就从这几个方面来入手解读,但首先,我们得先知道以下几个它的特点,以便于理解 AbstractQueuedSynchronizer特点 1.AQS可以实现独占锁和共享锁。 2.独占锁exclusive是一个悲观锁。保证只有一个线程经过一个阻塞点,只有一个线程可以获得锁。 3.共享锁shared是一个乐观锁

srs1995的博客 362

面试题:AQS(AbstractQueuedSynchronizer:抽象队列同步器)

悲观锁 多种实现 公平和非公平 volatile修饰的state变量 可重入 CAS保证并发安全性

人言束负兑生|完美是完蛋的开始 889

Gemini永久会员 Java AQS(AbstractQueuedSynchronizer,抽象队列同步器)优点

Java AQS(AbstractQueuedSynchronizer,抽象队列同步器)作为Java并发编程的核心框架,其优点体现在。

m0_37843156的博客 432

AbstractQueuedSynchronizer【抽象队列同步器】 -AQS基本原理整理记录

AQS 是什么? 一个抽象类 public abstract class AbstractQueuedSynchronizer 作用: 提供一个框架来实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关的同步器(信号量、事件等) Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-ou.

何xiao树 431

AQS抽象队列同步器

AQS

scochang的博客 127
上一篇: 【计算机网络】TCP的三次握手和四次挥手
下一篇: MVCC底层实现原理
聪明不喝牛奶
博客等级 码龄6年 7017粉丝 119原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

聪明不喝牛奶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值