ArrayList为什么会出现并发问题以及相应的解决办法

Java arrayList工作原理详解及并发 一.arrayList 概述 1.Java API 中的官方描述: Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing 阅读详情

问题一:ArrayList为什么会出现并发问题?

ArrayList是线程不安全的,在多线程并发访问的时候可能会出现问题,如果想使用线程安全的集合类,java自带有vector,也就是说vector是线程安全的。但是arayList的底层是数组实现的,而且可以自动扩容,获得元素或者在数组尾段插入元素的效率高,所以说ArrayList有其独特的优势。

1.扩容实现

private transient Object[] elementData;
 
public void ensureCapacity(int minCapacity) {
	modCount++;
	int oldCapacity = elementData.length;
	if (minCapacity > oldCapacity) {
	    Object oldData[] = elementData;
	    int newCapacity = (oldCapacity * 3)/2 + 1;
    	    if (newCapacity < minCapacity)
		newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
	}
    }

 

elementData数组的初始容量是10,如果需要扩容时,使用 elementData = Arrays.copyOf(elementData, newCapacity)进行复制, elementData = Arrays.copyOf(elementData, newCapacity)底层的核心代码为:

System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));
该方法中,original指的是:elementData,旧数组,0指的是复制从elementDate的索引为0开始

copy新生成的数组,0,新数组的索引为0开始,最后一个参数Math.min(elementData.length,newCapacity)

在上面的源码中,它的意思就是把老数组中的数据复制到一个新生成的容量为newLength的数组中。

扩容的时候,如果在并发操作ArrayList的时候,可能会有数组索引越界的异常产生。

分析上源码可以看出,只有当minCapacipity>oldCapacipity时才发生扩容问题,假设minCapacipity=oldCapacipity=10,不发生扩容问题,元素是可以被插入的。大家可以看下面的add源码,也就是size=9时可以将元素添加到数组中,多线程示意图如下:

线程A和线程B获取的size都是9,线程A先插入元素e,这个时候elementData数组的大小为10,是正常情况下下次应该是要扩容的,但是线程B获取的size=9而不是10,在线程B中没有进行扩容,而是报出数组index越界异常。

2.add操作

public boolean add(E e) {
	ensureCapacity(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
    }

add操作中先进行扩容操作ensureCapacity(size+1),之后才添加数据到elementData这个数组的末端,但是这样的操作不是线程安全的,多线程操作的时候可能会出现数据覆盖的问题。

线程A执行了ArrayList的add方法,由于线程B获取到的size大小和线程A是一样的,此时的size大小应该是比原来的size要大1,但是B线程不知,所以B线程进行赋值的时候把A线程的值给覆盖,导致添加到数组中元素的个数其实是比逻辑上要少的。

package TestArrayList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {

	private static List<Integer> list = new ArrayList<Integer>();

	private static ExecutorService executorService = Executors.newFixedThreadPool(1000); //定长线程池1000

	private static class IncreaseTask extends Thread{
	    @Override
	    public void run() {
	        System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!");
	        for(int i =0; i < 100; i++){
	            list.add(i);
	        }
	        System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!");
	    }
	}

	public static void main(String[] args){
	    for(int i=0; i < 1000; i++){ //开启1000个线程
	        executorService.submit(new IncreaseTask());
	    }
	    executorService.shutdown();
	    while (!executorService.isTerminated()){
	        try {
	            Thread.sleep(1000*10);
	        }catch (InterruptedException e){
	            e.printStackTrace();
	        }
	    }
	    System.out.println("All task finished!");
	    System.out.println("list size is :" + list.size());
	}
}

结果:list某个随机值是99900,本应该是100000,所以add操作是线程不安全的。

问题二:如何避免ArrayList的并发问题?

(1)使用Collections.synchronizedList()方法对ArrayList对象进行包装

ArrayList<Integer> arraylist = Collections.synchronizedList(new ArrayList());

将问题一中的Arraylist改成Collections.synchronizedList(new ArrayList)生成,问题的结果是100000.

源码:

public static <T> List<T> synchronizedList(List<T> list) {
	return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<T>(list) :
                new SynchronizedList<T>(list));
    }

上述类的继承结构如上所示,SynchronizedList是SynchronizedRandomAccessList的父类,我们现在看下SynchronizedList的源码,了解下为什么SynchronizedList是线程安全的。

	SynchronizedList(List<E> list) {
	    super(list);
	    this.list = list;
	}
	SynchronizedList(List<E> list, Object mutex) {
            super(list, mutex);
	    this.list = list;
        }

	public boolean equals(Object o) {
	    synchronized(mutex) {return list.equals(o);}
        }
	public int hashCode() {
	    synchronized(mutex) {return list.hashCode();}
        }

	public E get(int index) {
	    synchronized(mutex) {return list.get(index);}
        }
	public E set(int index, E element) {
	    synchronized(mutex) {return list.set(index, element);}
        }
	public void add(int index, E element) {
	    synchronized(mutex) {list.add(index, element);}
        }
	public E remove(int index) {
	    synchronized(mutex) {return list.remove(index);}
        }

	public int indexOf(Object o) {
	    synchronized(mutex) {return list.indexOf(o);}
        }
	public int lastIndexOf(Object o) {
	    synchronized(mutex) {return list.lastIndexOf(o);}
        }

	public boolean addAll(int index, Collection<? extends E> c) {
	    synchronized(mutex) {return list.addAll(index, c);}
        }

	public ListIterator<E> listIterator() {
	    return list.listIterator(); // Must be manually synched by user
        }

	public ListIterator<E> listIterator(int index) {
	    return list.listIterator(index); // Must be manually synched by user
        }

	public List<E> subList(int fromIndex, int toIndex) {
	    synchronized(mutex) {
                return new SynchronizedList<E>(list.subList(fromIndex, toIndex),
                                            mutex);
            }
        }

        /**
         * SynchronizedRandomAccessList instances are serialized as
         * SynchronizedList instances to allow them to be deserialized
         * in pre-1.4 JREs (which do not have SynchronizedRandomAccessList).
         * This method inverts the transformation.  As a beneficial
         * side-effect, it also grafts the RandomAccess marker onto
         * SynchronizedList instances that were serialized in pre-1.4 JREs.
         *
         * Note: Unfortunately, SynchronizedRandomAccessList instances
         * serialized in 1.4.1 and deserialized in 1.4 will become
         * SynchronizedList instances, as this method was missing in 1.4.
         */
        private Object readResolve() {
            return (list instanceof RandomAccess
		    ? new SynchronizedRandomAccessList<E>(list)
		    : this);
        }
    }

关于mutex定义:


	final Collection<E> c;  // Backing Collection
	final Object mutex;     // Object on which to synchronize

	SynchronizedCollection(Collection<E> c) {
            if (c==null)
                throw new NullPointerException();
	    this.c = c;
            mutex = this;
        }
	SynchronizedCollection(Collection<E> c, Object mutex) {
	    this.c = c;
            this.mutex = mutex;
        }

易知,mutex指向的就是当前对象自己,所以SynchronizedList是线程安全的根本原因是使用Synchronized对SynchronizedList的add,delete等操作进行加锁,但是这种锁的力度很大,所以这种方式效率低下。

(2)使用并发容器CopyOnWriteArrayList

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();

源码:

    private static final long serialVersionUID = 8673264195747942595L;

    /** The lock protecting all mutators */
    transient final ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private volatile transient Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }
public boolean add(E e) {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
	    Object[] elements = getArray();
	    int len = elements.length;
	    Object[] newElements = Arrays.copyOf(elements, len + 1);
	    newElements[len] = e;
	    setArray(newElements);
	    return true;
	} finally {
	    lock.unlock();
	}
    }

从add方法知道:CopyOnWriteArrayList底层数组的扩容方式是一个一个地增加,而且每次把原来的元素通过Arrays.copy()方法copy到新数组中,然后在尾部加上新元素e.它的底层并发安全的保证是通过ReentrantLock进行保证的,CopyOnWriteArrayList和SynchronizedList的底层实现方式是不一样的,前者是通过Lock机制进行加锁,而后者是通过Synchronized进行加锁,至于两者的区别,下次详细描述。

对比实验

将问题一中的测试代码,修改如下,对比SynchronizedList和CopyOnWriteArrayList的执行时间:

package TestArrayList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
	
     //private static List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
	private static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<Integer>();

	private static ExecutorService executorService = Executors.newFixedThreadPool(1000); //定长线程池1000

	private static class IncreaseTask extends Thread{
	    @Override
	    public void run() {
	        System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!");
	        for(int i =0; i < 100; i++){
	            list.add(i);
	        }
	        System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!");
	    }
	}

	public static void main(String[] args){
		long start = System.currentTimeMillis();
	    for(int i=0; i < 1000; i++){ //开启1000个线程
	        executorService.submit(new IncreaseTask());
	    }
	    executorService.shutdown();
	    while (!executorService.isTerminated()){
	        try {
	            Thread.sleep(1000*10);
	        }catch (InterruptedException e){
	            e.printStackTrace();
	        }
	    }
	    System.out.println("All task finished!");
	    System.out.println("list size is :" + list.size());
	    long end = System.currentTimeMillis();
	    System.out.println("operation time:"+(double)(end-start)/1000+"s");
	}
}

实验结果:

左边是SynchronizedList,右边是CopyOnWriteArrayList,分析表明两者差别不大。

参考:

【1】http://www.cnblogs.com/areyouready/p/6803304.html

【2】https://www.cnblogs.com/vitasyuan/p/8955557.html

【3】https://blog.youkuaiyun.com/qing337197645/article/details/51219155

【4】https://my.oschina.net/jielucky/blog/167198

 

 

Handler与ArrayList在Android并发事件处理中的应用 关键字是Java提供的最基础的线程同步机制。它可以通过在方法或者代码块前面加上关键字,来保证同一时间只有一个线程可以执行该方法或代码块。是基于“写时复制(Copy-On-Write, COW)”机制实现的。这一机制的核心思想是:当有写操作发生时,首先复制一份当前列表的副本,然后在这个副本上执行写操作,写操作完成后再将原列表引用指向新的列表。这样,读操作仍然可以在原列表上无锁地进行,只有写操作涉及复制和替换的过程。因为写操作的这种复制成本较高,所以更适合读多写少的使用场景。 阅读详情

相关推荐

直升机空气动力学基础--005翼型的升阻比和气动力矩

焦点与翼型有关,总升力矩可以视为附加升力与零升力迎角升力的合成,对于对称翼型,零升力力矩为0,附加升力就是总升力,其焦点就是其压力中心。对于对称翼型,零升力力矩为0,对于非对称翼型,零升力力矩存在,与不对称度成正比。对于不对称翼型,升力对焦点的力矩保持不变。

ygf666的博客 4130

Java 解决 ArrayList 的并发问题

`ArrayList`是`java.util`包中的一个类,它不是线程安全的。如果多个线程同时对同一个`ArrayList`进行操作,可能会导致并发问题,如**数据不一致**或`ConcurrentModificationException`异常。`CopyOnWriteArrayList`是一种并发集合,它通过在写操作时创建一个新的副本来解决并发问题。这意味着读操作不会受到写操作的影响,而且不会抛出`ConcurrentModificationException`异常。

一只奋斗的猪 2776

i.MX RT1064引脚配置与BGA封装选型实战指南

在嵌入式硬件设计中,引脚配置与PCB布局是连接芯片手册与物理实现的关键桥梁,直接影响系统的稳定性与信号完整性。其核心原理在于理解处理器的IOMUX(输入/输出多路复用器)系统与多电源域架构,前者通过软件配置实现引脚功能的灵活复用,后者则为不同外设接口提供独立的电压基准,是保障电源稳定性的基石。从技术价值看,合理的引脚规划能规避信号干扰、电源不稳等潜在风险,提升设计一次成功率。在应用场景上,无论是连接SDRAM、USB、SDIO等高速外设,还是配置UART、I2C等中低速接口,都需要遵循特定的布局布线规则。本

weixin_33217202的博客 314

ArrayList多线程并发安全问题

ArrayList线程不安全的情况: t1 add时候,size++, 当执行到elementData[size] = e时,时间片用完了,t2 进行add,执行到elementData[size] = e,在数组相同的位置赋值,覆盖了t1的值,回到了t1,size++,t2,也size++,size=2 size++为多条指令的组合,多线程并发size++时,size会出现并发问题,小于预期结果 当数组元素达到临界值-1的时候(临界值为10,15,22,33…),比如到达了size=14,这时候t1执行

qq_44737446的博客 1060

【电子元件篇】电力电子中的变压器原理、作用、选型与测量指南-超简单解读

变压器虽小,却是电力电子的 “关键纽带”——从识别符号型号,到理解 “传交流隔直流” 的特点,再到按用途选参数、用万用表检测,只要抓住 “场景对应核心” 的逻辑,就能轻松掌握它的用法。

2301_81299416的博客 3024

【制作100个Unity游戏之35】从零开始构建类《月圆之夜》《杀戮尖塔》的卡牌游戏(附带项目源码)

本文介绍了如何从零开始构建类似《月圆之夜》《杀戮尖塔》的卡牌游戏。主要内容包括:1)获取游戏素材资源;2)导入xyframe框架工具集;3)实现手牌弯曲排列效果,通过样条曲线控制卡牌位置和旋转;4)设计可扩展的MVD卡牌系统架构,分离数据层(ScriptableObject存储)、模型层(业务逻辑)和视图层(显示控制)。文中提供了完整的代码实现,并展示了卡牌动态排列的GIF效果演示。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 1426

ArrayList在并发情况下的解决方案

ArrayList不是线程安全的 怎么证明呢? public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ try { list.add(UUID.

白鸽 1640

双端之Nginx+Php结合PostgreSQL搭建Wordpress

制作的 PG4WP 分支版本,它与所有版本的 WordPress 兼容,解决了原版的一些问题。参数,确保 PostgreSQL 正在监听默认端口(通常是 5432),或者你自定义的端口。:PG4WP 插件存在缺陷和不完整的功能,可能与最新版本的 WordPress 不兼容。的插件,它允许 WordPress 使用 PostgreSQL 数据库。,如果看到 Nginx 的默认欢迎页面,说明 Nginx 已正确安装。,如果看到 Nginx 的默认欢迎页面,说明 Nginx 已正确安装。

宇宙第一小趴菜的博客 1891

在多线程并发下,ArrayList不安全该如何解决

在多线程并发下,ArrayList不安全该如何解决

Li2992673708的博客 770

PCL SVD分解求变换矩阵【2026最新版】

该算例使用奇异矩阵分解来求变换矩阵,输入的是同名点坐标输出的是变换矩阵。博客长期更新,本文最近一次更新时间为:2026年5月10日。

点云侠的博客 36万+

OpenClaw人人养虾:接入Synology Chat

Synology Chat 是群晖 NAS(Network Attached Storage,网络附属存储)内置的即时通讯应用。

王国平的博客 780

arraylist 并发_聊聊ArrayList的问题

List这种数据结构可以说在我们的实际工作中经常会使用到,最常见的就是ArrayList和LinkedList两种实现类了,两种结构的操作看似相似,但是原理却差别巨大。关于这两者的差别,小编特意总结了一些干货和各位读者们进行分享。ArrayList和LinkedList的区别?通过阅读util包里面的源码可以很容易的看出两者的大致区别:ArrayList是一种具有动态扩容特性且基于数组基...

weixin_42467896的博客 599

推荐系统[一]:超详细知识介绍,一份完整的入门指南,解答推荐系统相关算法流程、衡量指标和应用,以及如何使用jieba分词库进行相似推荐,业界广告推荐技术最新进展

推荐算法其实本质上是一种信息处理逻辑,当获取了用户与内容的信息之后,按照一定的逻辑处理信息后,产生推荐结果。热度排行榜就是最简单的一种推荐方法,它依赖的逻辑就是当一个内容被大多数用户喜欢,那大概率其他用户也会喜欢。但是基于粗放的推荐往往会不够精确,想要挖掘用户个性化的,小众化的兴趣,需要制定复杂的规则运算逻辑,并由机器完成。召回:当用户以及内容量比较大的时候,往往先通过召回策略,将百万量级的内容先缩小到百量级。

丨汀、的博客 7591

ArrayList、HashMap、ConcurrentHashMap并发下出现的问题

一、 ArrayList ArrayList不是线程安全的,因为没有加锁。在并发环境下,会出现一些问题。 1.add()方法会出现数组越界问题。 ———先说说add()方法的内部实现,①step1先检查数组容量,②step2容量足够直接添加,容量不够扩容为原来1.5倍后添加。在说说为什么会出现数组越界问题,假设有两个线程在操作同一个ArrayList,线程一执行step1(容量足够)后被挂起...

qq_37019196的博客 954

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

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

Statistics and Machine Learning Toolbox

Statistics and Machine Learning Toolbox(带书签),MATLAB统计学习工具

19.并发下的ArrayList、HashMap,Integer加锁问题

import java.util.ArrayList; import java.util.Vector; /** * 并发下的ArrayList */ public class ArrayListMultiThread { static ArrayList<Integer> arrayList = new ArrayList<>(); // s...

345

IMSL CNL 7.0.0 (x86-64)Crack

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

Java中的ArrayList、LinkedList如何进行线程安全的操作、为什么ArrayList不是线程安全的?

后续操作这个List对象时,实际上修改的是原来 ArrayList 里的数据;在这个过程中,尽管两个线程都进行了扩容判断,但由于线程调度的原因,线程A完成了扩容,而线程B在扩容前就已经决定不再扩容,从而导致了线程B直接使用了旧的数组长度信息,进而访问了超出数组边界的位置(即。例如,在一个有大量读操作和少量写操作的系统中,像配置信息列表的读取和偶尔的修改场景,使用 CopyOnWriteArrayList 可以在保证线程安全的同时,提高读操作的并发性能。,它比较适用于读多写少的并发场景。

weixin_63172268的博客 2548

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

为什么要谨慎使用 ArrayList 的 subList 方法

作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。

smart_an的专栏 1002

concurrentarraylist_java多线程学习七::::并发下ArrayList和HashMap

本程序中的java环境是jdk1.8,之前的javajava多线程学习一到六的环境也是jdk1.8程序中的幽灵:隐蔽的错误package rs.thread.day0504;/*** @auther rs* @date 2019/5/4 16:23* @email 529811807@qq.com* @weixinhao javawjs*/public class Test06_error {pu...

weixin_39839162的博客 299

ArrayList为什么线程不安全

单线程环境下 单线程环境的ArrayList是不会有问题的 多线程环境 为什么ArrayList是线程不安全的?因为在进行写操作的时候,方法上为了保证并发性,是没有添加synchronized修饰,所以并发写的时候,就会出现问题 /** * @author HX * @title: ArrayListNotSafeDemo * @projectName spring_cloud1x * @date 2022/5/9 10:49 * ArrayList是线程不安全问题 */ public

qq_44723556的博客 490

源码分析为什么ArrayList是线程不安全的

源码分析为什么ArrayList是线程不安全的

weixin_50989637的博客 1526

并发下ArrayList不安全的解决办法,CopyOnWriteArraryList

1.为什么ArrayList不安全,Vector就安全呢?(你就开始想了,如果并发不出错,是不是得需要synchronized修饰add这个函数) 然后你点进add看看找到相应的ArrayList。add和Vector。add看看 这个时候,你会发现Vector的add有synchronized修饰(搞定) 但这个时候你就想,那为什么ArrayList的add没有synchronized修饰,开发者傻了吗??? 两个文件往上找,你会发现Vector比ArrayList早开发,别人早就想到了。..

weixin_42470290的博客 231

JUC之线程不安全的ArrayList、HashSet、HashMap

JUC之线程不安全的ArrayList、HashSet、HashMap1 线程不安全的ArrayList1.1 前言1.2 单线程下的ArrayList1.3 多线程下的ArrayList1.4 线程不安全的ArrayList,解决方案1.4.1 方案一: Vector1.4.2 方案二: Collections.synchronized()1.4.3 方案三: 采用JUC里面的方法-CopyOnWriteArrayList2 线程不安全的HashSet2.1 单线程下的HashSet2.2 多线程下的Ha

weixin_44533129的博客 377

聊聊ArrayList的问题

List这种数据结构可以说在我们的实际工作中经常会使用到,最常见的就是ArrayList和LinkedList两种实现类了,两种结构的操作看似相似,但是原理却差别巨大。关...

Java笔记虾 1090

ArrayList你知道多少?

new ArrayList () 会创建一个容量为10的Object数组,ArrayList每次扩容是原来的一半(oldCapacity + (oldCapacity >> 1)),ArrayList是线程不安全的。 请你写出一个ArrayList线程不安全的代码 public static void main(String[] args) { listNotSafe(); } private static void listNotSafe() {

NULL 396

ArrayList和CopyOnWriteArrayList

ArrayList和CopyOnWriteArrayList 原文地址 ArrayList和CopyOnWriteArrayList 这篇文章的目的如下: 了解一下ArrayList和CopyOnWriteArrayList的增删改查实现原理。 看看为什么说ArrayList查询快而增删慢? CopyOnWriteArrayList为什么并发安全且性能比Vector好。 1. List接口 首...

kaven 1381
上一篇: 判断两个单链表是否有交点并且返回交点的值
下一篇: HTTPS请求的整个过程的详细分析
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值