2023Java高频必背并发编程面试题合集

15 篇文章 0 订阅
12 篇文章 0 订阅

1、简述Java内存模型(JMM)。

Java内存模型定义了程序中各种变量的访问规则
(1)所有变量都存储在主存,每个线程都有⾃⼰的⼯作内存;
(2)⼯作内存中保存了被该线程使⽤的变量的主存副本,线程对变量的所有操作都必须在⼯作空间进⾏,不能直接读写主内存数据;
(3)操作完成后,线程的⼯作内存通过缓存⼀致性协议将操作完的数据刷回主存。

2、简述as-if-serial。

编译器会对原始的程序进⾏指令重排序和优化。但不管怎么重排序,其结果都必须和⽤户原始程序输出的预定结果保持⼀致。

3、简述happens-before⼋⼤规则。

(1)程序次序规则:⼀个线程内,按照代码顺序,书写在前⾯的操作先⾏发⽣于书写在后⾯的操作;
(2)锁定规则:⼀个unLock操作先⾏发⽣于后⾯对同⼀个锁的lock操作;
(3)volatile变量规则:对⼀个变量的写操作先⾏发⽣于后⾯对这个变量的读操作;
(4)传递规则:如果操作A先⾏发⽣于操作B,⽽操作B⼜先⾏发⽣于操作C,则可以得出操作A先⾏发⽣于操作C;
(5)线程启动规则:Thread对象的start()⽅法先⾏发⽣于此线程的每⼀个动作;
(6)线程中断规则:对线程interrupt()⽅法的调⽤先⾏发⽣于被中断线程的代码检测到中断事件的发⽣;
(7)线程终结规则:线程中所有的操作都先⾏发⽣于线程的终⽌检测,我们可以通过Thread.join()⽅法结束、Thread.isAlive()的返回值⼿段检测到线程已经终⽌执⾏;
(8)对象终结规则:⼀个对象的初始化完成先⾏发⽣于他的finalize()⽅法的开始。

4、as-if-serial 和 happens-before 的区别。

as-if-serial 保证单线程程序的执⾏结果不变,happens-before 保证正确同步的多线程程序的执⾏结果不变。

5、简述原⼦性操作。

⼀个操作或者多个操作,要么全部执⾏并且执⾏的过程不会被任何因素打断,要么就都不执⾏,这就是原⼦性操作。

6、简述线程的可⻅性。

可⻅性指当⼀个线程修改了共享变量时,其他线程能够⽴即得知修改。volatile、synchronized、final 关键字都能保证可⻅性。

7、简述有序性。

虽然多线程存在并发和指令优化等操作,但在本线程内观察该线程的所有执⾏操作是有序的。

8、简述Java中volatile关键字作⽤。

(1)保证变量对所有线程的可⻅性。当⼀个线程修改了变量值,新值对于其他线程来说是⽴即可以得知的;
(2)禁⽌指令重排。使⽤ volatile 变量进⾏写操作,编译器在⽣成字节码时,会在指令序列中插⼊内存屏障来禁⽌特定类型的处理器进⾏重排序。

9、Java线程的实现⽅式。

(1)实现Runnable接⼝;
(2)继承Thread类;
(3)实现Callable接⼝。

10、简述Java线程的状态。

线程状态有 NEW、RUNNABLE、BLOCK、WAITING、TIMED_WAITING、THERMINATED。
(1)NEW:新建状态,线程被创建且未启动,此时还未调⽤ start ⽅法;
(2)RUNNABLE:运⾏状态。表示线程正在JVM中执⾏,但是这个执⾏,不⼀定真的在跑,也可能在排队等CPU;
(3)BLOCKED:阻塞状态。线程等待获取锁,锁还没获得;
(4)WAITING:等待状态。线程内run⽅法执⾏完Object.wait()/Thread.join()进⼊该状态;
(5)TIMED_WAITING:限期等待。在⼀定时间之后跳出状态。调⽤Thread.sleep(long) 、Object.wait(long)、Thread.join(long)进⼊状态。其中这些参数代表等待的时间;
(6)TERMINATED:结束状态。线程调⽤完run⽅法进⼊该状态。

11、简述线程通信的⽅式。

(1)volatile 关键词修饰变量,保证所有线程对变量访问的可⻅性;
(2)synchronized关键词。确保多个线程在同⼀时刻只能有⼀个处于⽅法或同步块中;
(3)wait/notify⽅法;
(4)IO通信。

12、简述线程池。

没有线程池的情况下,多次创建,销毁线程开销⽐较⼤。如果在开辟的线程执⾏完当前任务后复⽤已创建的线程,可以降低开销、控制最⼤并发数。
线程池创建线程时,会将线程封装成⼯作线程 Worker,Worker 在执⾏完任务后还会循环获取⼯作队列中的任务来执⾏。
将任务派发给线程池时,会出现以下⼏种情况:
(1)核⼼线程池未满,创建⼀个新的线程执⾏任务;
(2)如果核⼼线程池已满,⼯作队列未满,将线程存储在⼯作队列;
(3)如果⼯作队列已满,线程数⼩于最⼤线程数就创建⼀个新线程处理任务;
(4)如果超过⼤⼩线程数,按照拒绝策略来处理任务。
线程池参数:
(1)corePoolSize:常驻核⼼线程数。超过该值后如果线程空闲会被销毁;
(2)maximumPoolSize:线程池能够容纳同时执⾏的线程最⼤数;
(3)keepAliveTime:线程空闲时间,线程空闲时间达到该值后会被销毁,直到只剩下corePoolSize 个线程为⽌,避免浪费内存资源;
(4)workQueue:⼯作队列;
(5)threadFactory:线程⼯⼚,⽤来⽣产⼀组相同任务的线程;
(6)handler:拒绝策略。
拒绝策略有以下⼏种:
(1)AbortPolicy:丢弃任务并抛出异常;
(2)CallerRunsPolicy:重新尝试提交该任务;
(3)DiscardOldestPolicy 抛弃队列⾥等待最久的任务并把当前任务加⼊队列;
(4)DiscardPolicy 表示直接抛弃当前任务但不抛出异常。

13、简述Executor框架。

Executor框架⽬的是将任务提交和任务如何运⾏分离开来的机制。
⽤户不再需要从代码层考虑设计任务的提交运⾏,只需要调⽤Executor框架实现类的Execute⽅法就可以提交任务。

14、简述Executor的继承关系。

(1)Executor:⼀个接⼝,其定义了⼀个接收Runnable对象的⽅法executor,该⽅法接收⼀个Runable实例
执⾏这个任务。
(2)ExecutorService:Executor的⼦类接⼝,其定义了⼀个接收Callable对象的⽅法,返回 Future 对象,
同时提供execute⽅法。
(3)ScheduledExecutorService:ExecutorService的⼦类接⼝,⽀持定期执⾏任务。
(4)AbstractExecutorService:抽象类,提供 ExecutorService 执⾏⽅法的默认实现。
(5)Executors:实现ExecutorService接⼝的静态⼯⼚类,提供了⼀系列⼯⼚⽅法⽤于创建线程池。
(6)ThreadPoolExecutor:继承AbstractExecutorService,⽤于创建线程池。
(7)ForkJoinPool: 继承AbstractExecutorService,Fork 将⼤任务分叉为多个⼩任务,然后让⼩任务执⾏,
Join 是获得⼩任务的结果,类似于map reduce。
(8)ThreadPoolExecutor:继承ThreadPoolExecutor,实现ScheduledExecutorService,⽤于创建带定时
任务的线程池。

15、简述线程池的状态。

(1)Running:能接受新提交的任务,也可以处理阻塞队列的任务。
(2)Shutdown:不再接受新提交的任务,但可以处理存量任务,线程池处于running时调⽤shutdown⽅法,会进⼊该状态。
(3)Stop:不接受新任务,不处理存量任务,调⽤shutdownnow进⼊该状态。
(4)Tidying:所有任务已经终⽌了,worker_count(有效线程数)为0。
(5)Terminated:线程池彻底终⽌。在tidying模式下调⽤terminated⽅法会进⼊该状态。

16、简述线程池类型。

(1)newCachedThreadPool 可缓存线程池,可设置最⼩线程数和最⼤线程数,线程空闲1分钟后⾃动销毁。
(2)newFixedThreadPool 指定⼯作线程数量线程池。
(3)newSingleThreadExecutor 单线程Executor。
(4)newScheduleThreadPool ⽀持定时任务的指定⼯作线程数量线程池。
(5)newSingleThreadScheduledExecutor ⽀持定时任务的单线程Executor。

17、简述阻塞队列。

阻塞队列是⽣产者消费者的实现具体组件之⼀。当阻塞队列为空时,从队列中获取元素的操作将会被阻塞,当阻塞队列满了,往队列添加元素的操作将会被阻塞。具体实现有:
(1)ArrayBlockingQueue:底层是由数组组成的有界阻塞队列。
(2)LinkedBlockingQueue:底层是由链表组成的有界阻塞队列。
(3)PriorityBlockingQueue:阻塞优先队列。
(4)DelayQueue:创建元素时可以指定多久才能从队列中获取当前元素
(5)SynchronousQueue:不存储元素的阻塞队列,每⼀个存储必须等待⼀个取出操作
(6)LinkedTransferQueue:与LinkedBlockingQueue相⽐多⼀个transfer⽅法,即如果当前有消费者正等待接收元素,可以把⽣产者传⼊的元素⽴刻传输给消费者。
(7)LinkedBlockingDeque:双向阻塞队列。

18、谈⼀谈ThreadLocal。

ThreadLocal 是线程共享变量。ThreadLoacl 有⼀个静态内部类 ThreadLocalMap,其 Key 是 ThreadLocal对象,值是 Entry 对象,ThreadLocalMap是每个线程私有的。
(1)set 给ThreadLocalMap设置值。
(2)get 获取ThreadLocalMap。
(3)remove 删除ThreadLocalMap类型的对象。
存在的问题:对于线程池,由于线程池会重⽤ Thread 对象,因此与 Thread 绑定的 ThreadLocal 也会被重⽤,造成⼀系列问题。
⽐如说内存泄漏。由于 ThreadLocal 是弱引⽤,但 Entry 的 value 是强引⽤,因此当 ThreadLocal 被垃圾回收后,value 依旧不会被释放,产⽣内存泄漏。

19、聊聊你对Java并发包下unsafe类的理解。

对于 Java 语⾔,没有直接的指针组件,⼀般也不能使⽤偏移量对某块内存进⾏操作。这些操作相对来讲是安全(safe)的。
Java 有个类叫 Unsafe 类,这个类使 Java 拥有了像 C 语⾔的指针⼀样操作内存空间的能⼒,同时也带来了指针的问题。这个类可以说是 Java 并发开发的基础。

20、Java中的乐观锁与CAS算法。

乐观锁认为数据发送时发⽣并发冲突的概率不⼤,所以读操作前不上锁。
到了写操作时才会进⾏判断,数据在此期间是否被其他线程修改。如果发⽣修改,那就返回写⼊失败;如果没有被修改,那就执⾏修改操作,返回修改成功。
乐观锁⼀般都采⽤ Compare And Swap(CAS)算法进⾏实现。顾名思义,该算法涉及到了两个操作,⽐较(Compare)和交换(Swap)。
CAS 算法的思路如下:
(1)该算法认为不同线程对变量的操作时产⽣竞争的情况⽐较少。
(2)该算法的核⼼是对当前读取变量值 E 和内存中的变量旧值 V 进⾏⽐较。
(3)如果相等,就代表其他线程没有对该变量进⾏修改,就将变量值更新为新值 N。
(4)如果不等,就认为在读取值 E 到⽐较阶段,有其他线程对变量进⾏过修改,不进⾏任何操作。

21、ABA问题及解决⽅法简述。

CAS 算法是基于值来做⽐较的,如果当前有两个线程,⼀个线程将变量值从 A 改为 B ,再由 B 改回为 A,当前线程开始执⾏ CAS 算法时,就很容易认为值没有变化,误认为读取数据到执⾏ CAS 算法的期间,没有线程修改过数据。
juc 包提供了⼀个 AtomicStampedReference,即在原始的版本下加⼊版本号戳,解决 ABA 问题。

22、简述常⻅的Atomic类。

在很多时候,我们需要的仅仅是⼀个简单的、⾼效的、线程安全的++或者–⽅案,使⽤synchronized关键字和lock固然可以实现,但代价⽐较⼤,此时⽤原⼦类更加⽅便。
基本数据类型的原⼦类有
(1)AtomicInteger 原⼦更新整形;
(2)AtomicLong 原⼦更新⻓整型;
(3)AtomicBoolean 原⼦更新布尔类型。
Atomic数组类型有
(1)AtomicIntegerArray 原⼦更新整形数组⾥的元素;
(2)AtomicLongArray 原⼦更新⻓整型数组⾥的元素;
(3)AtomicReferenceArray 原⼦更新引⽤类型数组⾥的元素。
Atomic引⽤类型有
(1)AtomicReference 原⼦更新引⽤类型;
(2)AtomicMarkableReference 原⼦更新带有标记位的引⽤类型,可以绑定⼀个 boolean 标记;
(3)AtomicStampedReference 原⼦更新带有版本号的引⽤类型。
FieldUpdater类型
(1)AtomicIntegerFieldUpdater 原⼦更新整形字段的更新器;
(2)AtomicLongFieldUpdater 原⼦更新⻓整形字段的更新器;
(3)AtomicReferenceFieldUpdater 原⼦更新引⽤类型字段的更新器。

23、简述Atomic类基本实现原理。

以AtomicIntger 为例。⽅法getAndIncrement,以原⼦⽅式将当前的值加1,具体实现为:
(1)在 for 死循环中取得 AtomicInteger ⾥存储的数值
(2)对 AtomicInteger 当前的值加 1
(3)调⽤ compareAndSet ⽅法进⾏原⼦更新
(4)先检查当前数值是否等于 expect
(5)如果等于则说明当前值没有被其他线程修改,则将值更新为 next,
(6)如果不是会更新失败返回 false,程序会进⼊ for 循环重新进⾏ compareAndSet 操作。
#2 4、简述CountDownLatch。
CountDownLatch这个类使⼀个线程等待其他线程各⾃执⾏完毕后再执⾏。是通过⼀个计数器来实现的,计数器的初始值是线程的数量。每当⼀个线程执⾏完毕后,调⽤countDown⽅法,计数器的值就减1,当计数器的值为0时,表示所有线程都执⾏完毕,然后在等待的线程就可以恢复⼯作了。只能⼀次性使⽤,不能reset。

25、简述CyclicBarrier。

CyclicBarrier 主要功能和CountDownLatch类似,也是通过⼀个计数器,使⼀个线程等待其他线程各⾃执⾏完毕后再执⾏。但是其可以重复使⽤(reset)。

26、简述Semaphore。

Semaphore即信号量。Semaphore 的构造⽅法参数接收⼀个 int 值,设置⼀个计数器,表示可⽤的许可数量即最⼤并发数。使⽤ acquire ⽅法获得⼀个许可证,计数器减⼀,使⽤ release ⽅法归还许可,计数器加⼀。如果此时计数器值为0,线程进⼊休眠。

27、简述Exchanger。

Exchanger类可⽤于两个线程之间交换信息。可简单地将Exchanger对象理解为⼀个包含两个格⼦的容器,通过exchanger⽅法可以向两个格⼦中填充信息。线程通过exchange ⽅法交换数据,第⼀个线程执⾏exchange ⽅法后会阻塞等待第⼆个线程执⾏该⽅法。当两个线程都到达同步点时这两个线程就可以交换数据当两个格⼦中的均被填充时,该对象会⾃动将两个格⼦的信息交换,然后返回给线程,从⽽实现两个线程的信息交换。

28、简述ConcurrentHashMap。

JDK7采⽤锁分段技术。⾸先将数据分成 Segment 数据段,然后给每⼀个数据段配⼀把锁,当⼀个线程占⽤锁访问其中⼀个段的数据时,其他段的数据也能被其他线程访问。
get 除读到空值不需要加锁。该⽅法先经过⼀次再散列,再⽤这个散列值通过散列运算定位到 Segment,最后通过散列算法定位到元素。put 须加锁,⾸先定位到 Segment,然后进⾏插⼊操作,第⼀步判断是否需要对 Segment ⾥的 HashEntry 数组进⾏扩容,第⼆步定位添加元素的位置,然后将其放⼊数组。
JDK8的改进
(1)取消分段锁机制,采⽤CAS算法进⾏值的设置,如果CAS失败再使⽤ synchronized 加锁添加元素;
(2)引⼊红⿊树结构,当某个槽内的元素个数超过8且 Node数组 容量⼤于 64 时,链表转为红⿊树;
(3)使⽤了更加优化的⽅式统计集合内的元素数量。

29、synchronized底层实现原理。

Java 对象底层都会关联⼀个 monitor,使⽤ synchronized 时 JVM 会根据使⽤环境找到对象的 monitor,根据 monitor 的状态进⾏加解锁的判断。如果成功加锁就成为该 monitor 的唯⼀持有者,monitor 在被释放前不能再被其他线程获取。
synchronized在JVM编译后会产⽣monitorenter 和 monitorexit 这两个字节码指令,获取和释放 monitor。
这两个字节码指令都需要⼀个引⽤类型的参数指明要锁定和解锁的对象,对于同步普通⽅法,锁是当前实例对象;对于静态同步⽅法,锁是当前类的 Class 对象;对于同步⽅法块,锁是synchronized 括号⾥的对象。
执⾏ monitorenter 指令时,⾸先尝试获取对象锁。如果这个对象没有被锁定,或当前线程已经持有锁,就把锁的计数器加 1,执⾏ monitorexit 指令时会将锁计数器减 1。⼀旦计数器为 0 锁随即就被释放。

30、synchronized关键词使⽤⽅法。

(1)直接修饰某个实例⽅法;
(2)直接修饰某个静态⽅法;
(3)修饰代码块。

31、简述Java偏向锁。

JDK 1.6 中提出了偏向锁的概念。该锁提出的原因是,开发者发现多数情况下锁并不存在竞争,⼀把锁往往是由同⼀个线程获得的。偏向锁并不会主动释放,这样每次偏向锁进⼊的时候都会判断该资源是否是偏向⾃⼰的,如果是偏向⾃⼰的则不需要进⾏额外的操作,直接可以进⼊同步操作。
其申请流程为
(1)⾸先需要判断对象的 Mark Word 是否属于偏向模式,如果不属于,那就进⼊轻量级锁判断逻辑。否则继续下⼀步判断;
(2)判断⽬前请求锁的线程 ID 是否和偏向锁本身记录的线程 ID ⼀致。如果⼀致,继续下⼀步的判断,如果不⼀致,跳转到步骤4;
(3)判断是否需要重偏向。如果不⽤的话,直接获得偏向锁;
(4)利⽤ CAS 算法将对象的 Mark Word 进⾏更改,使线程 ID 部分换成本线程 ID。如果更换成功,则重偏向完成,获得偏向锁。如果失败,则说明有多线程竞争,升级为轻量级锁。

32、简述轻量级锁。

轻量级锁是为了在没有竞争的前提下减少重量级锁出现并导致的性能消耗
其申请流程为:
(1)如果同步对象没有被锁定,虚拟机将在当前线程的栈帧中建⽴⼀个锁记录空间,存储锁对象⽬前 MarkWord 的拷⻉;
(2)虚拟机使⽤ CAS 尝试把对象的 Mark Word 更新为指向锁记录的指针;
(3)如果更新成功即代表该线程拥有了锁,锁标志位将转变为 00,表示处于轻量级锁定状态;
(4)如果更新失败就意味着⾄少存在⼀条线程与当前线程竞争。虚拟机检查对象的 Mark Word 是否指向当前线程的栈帧;
(5)如果指向当前线程的栈帧,说明当前线程已经拥有了锁,直接进⼊同步块继续执⾏;
(6)如果不是则说明锁对象已经被其他线程抢占;
(7)如果出现两条以上线程争⽤同⼀个锁,轻量级锁就不再有效,将膨胀为重量级锁,锁标志状态变为10,此时Mark Word 存储的就是指向重量级锁的指针,后⾯等待锁的线程也必须阻塞。

33、简述锁优化策略。

即⾃适应⾃旋、锁消除、锁粗化、锁升级等策略。

34、简述Java的⾃旋锁。

线程获取锁失败后,可以采⽤这样的策略,可以不放弃 CPU ,不停的重试内重试,这种操作也称为⾃旋锁。

35、简述⾃适应⾃旋锁。

⾃适应⾃旋锁⾃旋次数不再⼈为设定,通常由前⼀次在同⼀个锁上的⾃旋时间及锁的拥有者的状态决定。

36、简述锁粗化。

锁粗化的思想就是扩⼤加锁范围,避免反复的加锁和解锁。

37、简述锁消除。

锁消除是⼀种更为彻底的优化,在编译时,Java编译器对运⾏上下⽂进⾏扫描,去除不可能存在共享资源竞争的锁。

38、简述Lock与ReentrantLock。

Lock接⼝是 Java并发包的顶层接⼝。
可重⼊锁 ReentrantLock 是 Lock 最常⻅的实现,与 synchronized ⼀样可重⼊。ReentrantLock 在默认情况下是⾮公平的,可以通过构造⽅法指定公平锁。⼀旦使⽤了公平锁,性能会下降。

39、简述AQS。

AQS(AbstractQuenedSynchronizer)抽象的队列式同步器。AQS是将每⼀条请求共享资源的线程封装成⼀个锁队列的⼀个结点(Node),来实现锁的分配。AQS是⽤来构建锁或其他同步组件的基础框架,它使⽤⼀个 volatile int state 变量作为共享资源,如果线程获取资源失败,则进⼊同步队列等待;如果获取成功就执⾏临界区代码,释放资源时会通知同步队列中的等待线程。
⼦类通过继承同步器并实现它的抽象⽅法getState、setState 和 compareAndSetState对同步状态进⾏更改。
AQS获取独占锁/释放独占锁原理
(1)获取(acquire)
调⽤ tryAcquire ⽅法安全地获取线程同步状态,获取失败的线程会被构造同步节点并通过 addWaiter⽅法加⼊到同步队列的尾部,在队列中⾃旋;
调⽤ acquireQueued ⽅法使得该节点以死循环的⽅式获取同步状态,如果获取不到则阻塞。
(2)释放(release)
调⽤ tryRelease ⽅法释放同步状态;
调⽤ unparkSuccessor ⽅法唤醒头节点的后继节点,使后继节点重新尝试获取同步状态。
AQS获取共享锁/释放共享锁原理:
获取锁(acquireShared)
调⽤ tryAcquireShared ⽅法尝试获取同步状态,返回值不⼩于 0 表示能获取同步状态。
释放(releaseShared),并唤醒后续处于等待状态的节点。

40、死锁与活锁的区别,死锁与饥饿的区别?

死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成
的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
产生死锁的必要条件
(1)互斥条件:所谓互斥就是进程在某一时间内独占资源。
(2)请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3)不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,
失败,尝试,失败。
活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而
处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执
行的状态。
Java 中导致饥饿的原因:
(1)高优先级线程吞噬所有的低优先级线程的 CPU 时间。
(2)线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前
持续地对该同步块进行访问。
(3)线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方
法),因为其他线程总是被持续地获得唤醒。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Z_凌云

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

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

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

打赏作者

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

抵扣说明:

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

余额充值