Java关键字——synchronized

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

修饰一个方法

被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

修饰一个静态的方法

其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

修饰一个代码块

被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

修饰一个类

其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

 

修饰一个方法

被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

如果多个线程访问同一个对象的实例变量,可能出现非线程安全问题。

例子:a线程set后sleep2000ms,看b线程是否可以趁机set,造成非线程安全

HasSelfPrivateNum.java:


    
    
  1. package service;
  2. public class HasSelfPrivateNum {
  3. private int num = 0;
  4. public void addI (String username) {
  5. try {
  6. if (username.equals( "a")) {
  7. num = 100;
  8. System.out.println( "a set over!");
  9. Thread.sleep( 2000);
  10. } else {
  11. num = 200;
  12. System.out.println( "b set over!");
  13. }
  14. System.out.println(username + " num=" + num);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }

A:


 
 
  1. package extthread;
  2. import service.HasSelfPrivateNum;
  3. public class ThreadA extends Thread {
  4. private HasSelfPrivateNum numRef;
  5. public ThreadA (HasSelfPrivateNum numRef) {
  6. super();
  7. this.numRef = numRef;
  8. }
  9. @Override
  10. public void run () {
  11. super.run();
  12. numRef.addI( "a");
  13. }
  14. }

B:


 
 
  1. package extthread;
  2. import service.HasSelfPrivateNum;
  3. public class ThreadB extends Thread {
  4. private HasSelfPrivateNum numRef;
  5. public ThreadB (HasSelfPrivateNum numRef) {
  6. super();
  7. this.numRef = numRef;
  8. }
  9. @Override
  10. public void run () {
  11. super.run();
  12. numRef.addI( "b");
  13. }
  14. }

run:


 
 
  1. package test;
  2. import service.HasSelfPrivateNum;
  3. import extthread.ThreadA;
  4. import extthread.ThreadB;
  5. public class Run {
  6. public static void main (String[] args) {
  7. HasSelfPrivateNum numRef = new HasSelfPrivateNum();
  8. ThreadA athread = new ThreadA(numRef);
  9. athread.start();
  10. ThreadB bthread = new ThreadB(numRef);
  11. bthread.start();
  12. }
  13. }

结果:a线程set后sleep2000ms,b线程可以趁机set,造成非线程安全

这时我们使用synchronized修饰addI方法:


 
 
  1. package service;
  2. public class HasSelfPrivateNum {
  3. private int num = 0;
  4. synchronized public void addI (String username) {
  5. try {
  6. if (username.equals( "a")) {
  7. num = 100;
  8. System.out.println( "a set over!");
  9. Thread.sleep( 2000);
  10. } else {
  11. num = 200;
  12. System.out.println( "b set over!");
  13. }
  14. System.out.println(username + " num=" + num);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }

结果:B不能set了,说明线程安全。

注意:我们取得的是对象锁,也就是说,一个对象一个锁,而不是锁住整个类或者代码或者方法。

例子:两个对象两个锁

myobject.java

打印名字后sleep,最后打印end


 
 
  1. package extobject;
  2. public class MyObject {
  3. synchronized public void methodA () {
  4. try {
  5. System.out.println( "begin methodA threadName="
  6. + Thread.currentThread().getName());
  7. Thread.sleep( 5000);
  8. System.out.println( "end");
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. }

A:


 
 
  1. package extthread;
  2. import extobject.MyObject;
  3. public class ThreadA extends Thread {
  4. private MyObject object;
  5. public ThreadA (MyObject object) {
  6. super();
  7. this.object = object;
  8. }
  9. @Override
  10. public void run () {
  11. super.run();
  12. object.methodA();
  13. }
  14. }

B:


 
 
  1. package extthread;
  2. import extobject.MyObject;
  3. public class ThreadB extends Thread {
  4. private MyObject object;
  5. public ThreadB (MyObject object) {
  6. super();
  7. this.object = object;
  8. }
  9. @Override
  10. public void run () {
  11. super.run();
  12. object.methodA();
  13. }
  14. }

RUN:


 
 
  1. package test.run;
  2. import extobject.MyObject;
  3. import extthread.ThreadA;
  4. import extthread.ThreadB;
  5. public class Run {
  6. public static void main (String[] args) {
  7. MyObject object = new MyObject();
  8. ThreadA a = new ThreadA(object);
  9. a.setName( "A");
  10. ThreadB b = new ThreadB(object);
  11. b.setName( "B");
  12. a.start();
  13. b.start();
  14. }
  15. }

结果:两个对象互不影响,各自运行各自上锁

其它方法被调用是什么效果呢?

之前做实验是因为怕大家不理解知识,现在大家已经有了一定的基础,这个结论不再做实验。

结论:

如果A线程持有x对象的锁,B线程不可调用synchronized修饰的方法,但是可以异步调用没有被synchronized修饰的方法

 

synchronized具有锁重入功能,也就是说一个线程获得锁,再次请求是可以再次得到对象的锁的

下面做实验验证这个结论:

service.java


 
 
  1. package myservice;
  2. public class Service {
  3. synchronized public void service1 () {
  4. System.out.println( "service1");
  5. service2();
  6. }
  7. synchronized public void service2 () {
  8. System.out.println( "service2");
  9. service3();
  10. }
  11. synchronized public void service3 () {
  12. System.out.println( "service3");
  13. }
  14. }

thread:


 
 
  1. package extthread;
  2. import myservice.Service;
  3. public class MyThread extends Thread {
  4. @Override
  5. public void run () {
  6. Service service = new Service();
  7. service.service1();
  8. }
  9. }

run:


 
 
  1. package test;
  2. import extthread.MyThread;
  3. public class Run {
  4. public static void main (String[] args) {
  5. MyThread t = new MyThread();
  6. t.start();
  7. }
  8. }

结果验证了上面的结论:

注:在父子类之间同样适用,不再做实验

 

但是如果一个线程出现了异常,难道就永远锁住了资源吗?其实不是的,出现异常自动释放锁

实验:让a锁住对象后出现异常,看b是否可以拿到锁,代码不再展示。

结果:b可以正常执行。

 

修饰一个静态的方法

 

其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

也就是说整个类就一个锁,这也和静态的概念相符(静态方法和属性是属于类的而不是具体一个对象的)

让我们来验证这个结论:

service:


 
 
  1. package service;
  2. public class Service {
  3. synchronized public static void printA () {
  4. try {
  5. System.out.println( "线程名称为:" + Thread.currentThread().getName()
  6. + "在" + System.currentTimeMillis() + "进入printA");
  7. Thread.sleep( 3000);
  8. System.out.println( "线程名称为:" + Thread.currentThread().getName()
  9. + "在" + System.currentTimeMillis() + "离开printA");
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. synchronized public static void printB () {
  15. System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在"
  16. + System.currentTimeMillis() + "进入printB");
  17. System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在"
  18. + System.currentTimeMillis() + "离开printB");
  19. }
  20. }

a:


 
 
  1. package extthread;
  2. import service.Service;
  3. public class ThreadA extends Thread {
  4. private Service service;
  5. public ThreadA (Service service) {
  6. super();
  7. this.service = service;
  8. }
  9. @Override
  10. public void run () {
  11. service.printA();
  12. }
  13. }

b:


 
 
  1. package extthread;
  2. import service.Service;
  3. public class ThreadB extends Thread {
  4. private Service service;
  5. public ThreadB (Service service) {
  6. super();
  7. this.service = service;
  8. }
  9. @Override
  10. public void run () {
  11. service.printB();
  12. }
  13. }

run:


 
 
  1. package test;
  2. import service.Service;
  3. import extthread.ThreadA;
  4. import extthread.ThreadB;
  5. public class Run {
  6. public static void main (String[] args) {
  7. Service service1 = new Service();
  8. Service service2 = new Service();
  9. ThreadA a = new ThreadA(service1);
  10. a.setName( "A");
  11. a.start();
  12. ThreadB b = new ThreadB(service2);
  13. b.setName( "B");
  14. b.start();
  15. }
  16. }

结果:

但是,请注意一个显而易见的结论:a线程访问synchronized修饰的静态方法时,b线程可以访问synchronized修饰的非静态方法,原因也很容易想到:静态方法是属于类的,普通方法是属于对象本身的,所以一个是对象锁,一个是class锁,不会影响。

为了验证这个结论,我们做实验看看结果。

service:

AB为静态的,C普通的。


 
 
  1. package service;
  2. public class Service {
  3. synchronized public static void printA () {
  4. try {
  5. System.out.println( "线程名称为:" + Thread.currentThread().getName()
  6. + "在" + System.currentTimeMillis() + "进入printA");
  7. Thread.sleep( 3000);
  8. System.out.println( "线程名称为:" + Thread.currentThread().getName()
  9. + "在" + System.currentTimeMillis() + "离开printA");
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. synchronized public static void printB () {
  15. System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在"
  16. + System.currentTimeMillis() + "进入printB");
  17. System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在"
  18. + System.currentTimeMillis() + "离开printB");
  19. }
  20. synchronized public void printC () {
  21. System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在"
  22. + System.currentTimeMillis() + "进入printC");
  23. System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在"
  24. + System.currentTimeMillis() + "离开printC");
  25. }
  26. }

a: 


 
 
  1. package extthread;
  2. import service.Service;
  3. public class ThreadA extends Thread {
  4. private Service service;
  5. public ThreadA (Service service) {
  6. super();
  7. this.service = service;
  8. }
  9. @Override
  10. public void run () {
  11. service.printA();
  12. }
  13. }

b:


 
 
  1. package extthread;
  2. import service.Service;
  3. public class ThreadB extends Thread {
  4. private Service service;
  5. public ThreadB (Service service) {
  6. super();
  7. this.service = service;
  8. }
  9. @Override
  10. public void run () {
  11. service.printB();
  12. }
  13. }

c:


 
 
  1. package extthread;
  2. import service.Service;
  3. public class ThreadC extends Thread {
  4. private Service service;
  5. public ThreadC (Service service) {
  6. super();
  7. this.service = service;
  8. }
  9. @Override
  10. public void run () {
  11. service.printC();
  12. }
  13. }

 run:


 
 
  1. package test;
  2. import service.Service;
  3. import extthread.ThreadA;
  4. import extthread.ThreadB;
  5. import extthread.ThreadC;
  6. public class Run {
  7. public static void main (String[] args) {
  8. Service service = new Service();
  9. ThreadA a = new ThreadA(service);
  10. a.setName( "A");
  11. a.start();
  12. ThreadB b = new ThreadB(service);
  13. b.setName( "B");
  14. b.start();
  15. ThreadC c = new ThreadC(service);
  16. c.setName( "C");
  17. c.start();
  18. }
  19. }

结果:

说明:验证了结论,因为AB同步执行,C异步执行。

 

修饰一个代码块

被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;(而不一定是本对象了)

synchronized方法是对当前对象加锁,synchronized代码块是对某个对象加锁

用synchronized声明方法是有弊端的,比如A调用同步方法执行一个很长时间的任务,这时B就必须等待,有时候这种长时间等待是低效率且没有必要的,这时我们就要认识一下synchronized同步代码块了。

格式是这样的:synchronized(类名){......}

我们先来认识第一种用法来体会修饰代码块的好处:

synchronized(this)同步代码块:a调用相关代码后,b对其它synchronized方法和synchronized(this)同步代码块调用会阻塞。但是没有被synchronized修饰的代码就得以执行,不像之前修饰方法那么死板了。

我们来看一个例子:

第一个循环异步,第二个循环同步:


 
 
  1. package mytask;
  2. public class Task {
  3. public void doLongTimeTask () {
  4. for ( int i = 0; i < 100; i++) {
  5. System.out.println( "nosynchronized threadName="
  6. + Thread.currentThread().getName() + " i=" + (i + 1));
  7. }
  8. System.out.println( "");
  9. synchronized ( this) {
  10. for ( int i = 0; i < 100; i++) {
  11. System.out.println( "synchronized threadName="
  12. + Thread.currentThread().getName() + " i=" + (i + 1));
  13. }
  14. }
  15. }
  16. }

thread1:


 
 
  1. package mythread;
  2. import mytask.Task;
  3. public class MyThread1 extends Thread {
  4. private Task task;
  5. public MyThread1 (Task task) {
  6. super();
  7. this.task = task;
  8. }
  9. @Override
  10. public void run () {
  11. super.run();
  12. task.doLongTimeTask();
  13. }
  14. }

thread2:


 
 
  1. package mythread;
  2. import mytask.Task;
  3. public class MyThread2 extends Thread {
  4. private Task task;
  5. public MyThread2 (Task task) {
  6. super();
  7. this.task = task;
  8. }
  9. @Override
  10. public void run () {
  11. super.run();
  12. task.doLongTimeTask();
  13. }
  14. }

run:


 
 
  1. package test;
  2. import mytask.Task;
  3. import mythread.MyThread1;
  4. import mythread.MyThread2;
  5. public class Run {
  6. public static void main (String[] args) {
  7. Task task = new Task();
  8. MyThread1 thread1 = new MyThread1(task);
  9. thread1.start();
  10. MyThread2 thread2 = new MyThread2(task);
  11. thread2.start();
  12. }
  13. }

结果:

在非同步代码块时,是交叉打印的

同步代码块时排队执行:

另一个用法:

synchronized(非this)同步代码块(也就是说将任意对象作为对象监视器):

格式:synchronized(非this对象x){......}

1、当多个线程持有的对象监听器为同一个对象时,依旧是同步的,同一时间只有一个可以访问,

2、但是对象不同,执行就是异步的。

这样有什么好处呢?

(因为如果一个类有很多synchronized方法或synchronized(this)代码块,还是会影响效率,这时用synchronized(非this)同步代码块就不会和其它锁this同步方法争抢this锁)

实验

第一点我们就不验证了,因为体现不出它的好处,我们验证第二点:

service:

一个同步非this代码块,一个同步方法:


 
 
  1. package service;
  2. public class Service {
  3. private String anyString = new String();
  4. public void a () {
  5. try {
  6. synchronized (anyString) {
  7. System.out.println( "a begin");
  8. Thread.sleep( 3000);
  9. System.out.println( "a end");
  10. }
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. synchronized public void b () {
  16. System.out.println( "b begin");
  17. System.out.println( "b end");
  18. }
  19. }

a:


 
 
  1. package extthread;
  2. import service.Service;
  3. public class ThreadA extends Thread {
  4. private Service service;
  5. public ThreadA (Service service) {
  6. super();
  7. this.service = service;
  8. }
  9. @Override
  10. public void run () {
  11. service.a();
  12. }
  13. }

b:


 
 
  1. package extthread;
  2. import service.Service;
  3. public class ThreadB extends Thread {
  4. private Service service;
  5. public ThreadB (Service service) {
  6. super();
  7. this.service = service;
  8. }
  9. @Override
  10. public void run () {
  11. service.b();
  12. }
  13. }

 run:


 
 
  1. package test;
  2. import service.Service;
  3. import extthread.ThreadA;
  4. import extthread.ThreadB;
  5. public class Run {
  6. public static void main (String[] args) {
  7. Service service = new Service();
  8. ThreadA a = new ThreadA(service);
  9. a.setName( "A");
  10. a.start();
  11. ThreadB b = new ThreadB(service);
  12. b.setName( "B");
  13. b.start();
  14. }
  15. }

结果:

确实是异步的。

最后一个要注意的点:我们知道synchronized(非this对象x){......}是将对象x监视,这也就意味着当线程a调用这段代码时,线程b调用类x中的同步方法和代码块也会是同步的效果(阻塞)。

为了让大家更明白,做最后一个例子:

首先创建一个有静态方法的类:


 
 
  1. package test2.extobject;
  2. public class MyObject {
  3. synchronized public void speedPrintString () {
  4. System.out.println( "speedPrintString ____getLock time="
  5. + System.currentTimeMillis() + " run ThreadName="
  6. + Thread.currentThread().getName());
  7. System.out.println( "-----------------");
  8. System.out.println( "speedPrintString releaseLock time="
  9. + System.currentTimeMillis() + " run ThreadName="
  10. + Thread.currentThread().getName());
  11. }
  12. }

然后用 synchronized(非this对象x){......}的形式引用它,并进入这个代码块,然后看看这时这个静态方法是否可以被调用。

service:作用是synchronized(object)


 
 
  1. package test2.service;
  2. import test2.extobject.MyObject;
  3. public class Service {
  4. public void testMethod1 (MyObject object) {
  5. synchronized (object) {
  6. try {
  7. System.out.println( "testMethod1 ____getLock time="
  8. + System.currentTimeMillis() + " run ThreadName="
  9. + Thread.currentThread().getName());
  10. Thread.sleep( 5000);
  11. System.out.println( "testMethod1 releaseLock time="
  12. + System.currentTimeMillis() + " run ThreadName="
  13. + Thread.currentThread().getName());
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. }
  19. }

threadA:


 
 
  1. package test2.extthread;
  2. import test2.extobject.MyObject;
  3. import test2.service.Service;
  4. public class ThreadA extends Thread {
  5. private Service service;
  6. private MyObject object;
  7. public ThreadA (Service service, MyObject object) {
  8. super();
  9. this.service = service;
  10. this.object = object;
  11. }
  12. @Override
  13. public void run () {
  14. super.run();
  15. service.testMethod1(object);
  16. }
  17. }

 threadB:


 
 
  1. package test2.extthread;
  2. import test2.extobject.MyObject;
  3. public class ThreadB extends Thread {
  4. private MyObject object;
  5. public ThreadB (MyObject object) {
  6. super();
  7. this.object = object;
  8. }
  9. @Override
  10. public void run () {
  11. super.run();
  12. object.speedPrintString();
  13. }
  14. }

run:

 


 
 
  1. package test2.run;
  2. import test2.extobject.MyObject;
  3. import test2.extthread.ThreadA;
  4. import test2.extthread.ThreadB;
  5. import test2.service.Service;
  6. public class Run {
  7. public static void main (String[] args) throws InterruptedException {
  8. Service service = new Service();
  9. MyObject object = new MyObject();
  10. ThreadA a = new ThreadA(service, object);
  11. a.setName( "a");
  12. a.start();
  13. Thread.sleep( 100);
  14. ThreadB b = new ThreadB(object);
  15. b.setName( "b");
  16. b.start();
  17. }
  18. }

结果:

 

是同步的。

当然,把修饰方法改为修饰代码块也是一样不能被执行的。

Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决重排序问题。

synchronized底层原理

在 Java 早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统来实现,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。

在 Java 6 之后从 JVM 层面对synchronized 较大优化,锁的实现引入了如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

 

synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令。

其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于每个Java对象的对象头中,synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权.当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。但是原理其实都是类似的。具体的实现是操作系统的知识可以去翻我操作系统的文章。

锁详解

锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率。

自旋:当有个线程A去请求某个锁的时候,这个锁正在被其它线程占用,但是线程A并不会马上进入阻塞状态,而是循环请求锁(自旋)。这样做的目的是因为很多时候持有锁的线程会很快释放锁的,线程A可以尝试一直请求锁,没必要被挂起放弃CPU时间片,因为线程被挂起然后到唤醒这个过程开销很大,当然如果线程A自旋指定的时间还没有获得锁,仍然会被挂起。

自适应性自旋:自适应性自旋是自旋的升级、优化,自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定。例如线程如果自旋成功了,那么下次自旋的次数会增多,因为JVM认为既然上次成功了,那么这次自旋也很有可能成功,那么它会允许自旋的次数更多。

锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。

偏向锁的目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用的互斥量,那么偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不用做了。偏向锁默认是开启的,也可以关闭。

 

转载自:
https://blog.csdn.net/hebtu666/article/details/103057476?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167656144816800182766614%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=167656144816800182766614&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-29-103057476-null-null.142v73insert_down2,201v4add_ask,239v2insert_chatgpt&utm_term=synchronized&spm=1018.2226.3001.4187

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值