Java-多线程知识点

进程,线程

并发

并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。即一个处理器同时处理多个任务,只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。(交替执行)

并行

并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)。 并行即多个处理器或者是多核的处理器同时处理多个不同的任务,两个线程互不抢占CPU资源,可以同时进行。(同时执行)

什么是进程

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。(进程是资源分配的最小单位
进程

什么是线程

线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位

线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

好处 :

(1)易于调度。
(2)提高并发性。通过线程可方便有效地实现并发性。进程可创建多个线程来执行同一程序的不同部分。
(3)开销少。创建线程比创建进程要快,所需开销很少。
(4)充分发挥多处理器的功能。通过创建多线程进程,每个线程在一个处理器上运行,从而实现应用程序的并发性,使每个处理器都得到充分运行。

进程与线程的区别:

(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
(4)系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。

什么是线程调度

计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待CPU,JAVA虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配CPU的使用权。

有两种调度模型:分时调度模型和抢占式调度模型。

  • 分时调度模型是指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU的时间片这个也比较好理解。
  • Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。

Java的线程调度是不分时的,同时启动多个线程后,不能保证各个线程轮流获得均等的CPU时间片。

如果希望明确地让一个线程给另外一个线程运行的机会,可以采取以下办法:

  • 调整各个线程的优先级
  • 让处于运行状态的线程调用Thread.sleep()方法
  • 让处于运行状态的线程调用Thread.yield()方法
  • 让处于运行状态的线程调用另一个线程的join()方法
  • 线程切换:不是所有的线程切换都需要进入内核模式
    线程调度
    需要注意的是,线程的调度不是跨平台的,它不仅仅取决于Java虚拟机,还依赖于操作系统。在某些操作系统中,只要运行中的线程没有遇到阻塞,就不会放弃CPU;在某些操作系统中,即使线程没有遇到阻塞,也会运行一段时间后放弃CPU,给其它线程运行的机会。

小结

  1. 并发和并行都可以处理“多任务”,二者的主要区别在于是否是“同时进行”多个的任务。
  2. 进程是资源分配的最小单位,线程是CPU调度的最小单位。
  3. 一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
  4. 线程的调度不是跨平台的,既取决于Java虚拟机,又依赖于操作系统。

线程操作

实现线程的两种方式

创建新执行线程有两种方法:

  • 一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建对象,开启线程。run方法相当于其他线程的main方法。
  • 另一种方法是声明一个实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。

继承Thread类实现线程

Java虚拟机允许应用程序同时执行多个执行线程。每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。Thread类是Runnable接口的实现类,可以是实现多线程。

Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是
完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。

Thread类构造方法:

  • public Thread() :分配一个新的 Thread对象。
  • public Thread(Runnable target) :分配一个新的 Thread对象。
  • public Thread(Runnable target, String name) :分配一个新的 Thread对象。
  • public Thread(String name) :分配一个新的 Thread对象。

Thread类常用方法:

  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
  • public String getName() :返回此线程的名称。
  • public int getPriority() :返回此线程的优先级。
  • public void join() :等待这个线程死亡。
  • public void join(long millis) :等待这个线程死亡最多 millis毫秒。
  • public void join(long millis, int nanos) :等待最多 millis毫秒加上 nanos纳秒这个线程死亡。
  • public void run() :如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
  • public void setName(String name) :将此线程的名称更改为等于参数 name 。
  • public void setPriority(int newPriority) :更改此线程的优先级。
  • public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
  • public static void sleep(long millis, int nanos) :导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停(临时停止执行),这取决于系统定时器和调度器的精度和准确性。
  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public static void yield() :对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。

继承Thread类创建线程的步骤:

  1. 定义子类,继承Thread类;
  2. 子类中重写Thread类中的run方法;
  3. 创建Thread子类对象,也就是创建线程对象;
  4. 调用线程对象的start方法启动线程。

【案例1】 继承Thread类实现多线程

代码实现:

package day18;

//继承Thread类
public class Myth01 extends Thread {
    //设置线程名称的属性
    private String name;

    //有参构造
    public Myth01(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        //for循环
        for (int i = 1; i <= 20 ; i++) {
            //输出
            System.out.println(this.name + i );
        }
    }
}
package day18;

//创建测试类
public class Test01 {
    //程序入口
    public static void main(String[] args) {
        //创建自定义线程对象
        Myth01 m1=new Myth01("自定义线程");
        //调用start方法来启动线程
        m1.start();

        //for循环
        for (int i = 1; i <= 20 ; i++) {
            //输出
            System.out.println("主线程" + i );
        }
    }
}

运行结果:

运行结果1

运行结果2

(每次运行的结果都是不一样的 )解释说明:

程序启动运行main时候,Java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的 start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。

那么为什么可以完成并发执行呢?我们再来讲一讲原理:

多线程执行时,到底在内存中是如何运行的呢?

多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。

当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

主线程:主线程是当一个程序启动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行。即main就是程序运行中的主线程。

主线程的重要性体现在两方面:

1.是产生其他子线程的线程;

2.通常它必须最后完成执行比如执行各种关闭动作。

子线程:就是我们自己手动创建的,并且在启动的时候只能调用start方法,不能调用run方法,run方法就是主线程了。

实现Runnable接口实现线程

java.lang .Runnable接口用来指定每个线程要执行的任务。包含了一个run 的无参数抽象方法,需要由接口实现类重写该方法。

  • public void run():当实现接口的对象 Runnable被用来创建一个线程,启动线程使对象的 run在独立执行的线程中调用的方法

实现Runnable接口创建线程的步骤:

  1. 定义子类,实现Runnable接口;

  2. 子类中重写Runnable接口中的run方法;

  3. 创建Runnable接口的子类对象;

  4. 通过Thread类的构造器创建线程对象;

  5. 调用Thread类的start方法启动线程。

【案例2】 实现Runnable接口实现多线程

代码实现:

package day18;

//创建Runnable接口实现类
public class Myth02 implements Runnable {
    @Override
    public void run() {
        //for循环
        for (int i = 1; i <= 20 ; i++) {
            //输出
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

package day18;

//创建测试类
public class Test02 {
    //程序入口
    public static void main(String[] args) {
        //创建线程对象
        Myth02 m2=new Myth02();
        //创建Thread类
        Thread t1=new Thread(m2,"线程一");
        Thread t2=new Thread(m2,"线程二");
        //调用start方法开启线程
        t1.start();
        t2.start();
    }
}

运行结果:

运行结果1

运行结果2
通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程 代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread 对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现 Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

两种创建线程方法的区别

  • 一个是继承Thread 类,一个是实现Runnable 接口。
  • Java是只能继承一个父类,可以实现多个接口的一个特性,所以说采用Runnable方式可以避免Thread方式由于Java单继承带来的缺陷。
  • Runnable的代码可以被多个线程共享(Thread实例),适合于多个多个线程处理统一资源的情况。

线程的状态

  • 新建(NEW):刚刚创建出来但是没有运行的线程处于此状态。
  • 运行(RUNNABLE):调用start方法启动后的线程处于运行状态。
  • 受阻塞(BLOCKED):等待获取锁的线程处于此状态。
  • 无限等待(WAITING):当线程调用wait()方法时,线程会处于无限等待状态【没有时间的等待】
  • 计时等待(TIMED_WAITING):当线程调用wait(毫秒值)方法或sleep(毫秒值)时,线程会处于计时等待状态【有时间的等待】
  • 退出(TERMINATED):当线程执行完了自己的run方法或者调用了stop方法,会进入退出状态。

线程状态转换图可参考此博客: 线程状态转换图及各部分介绍

阻塞的情况分三种:

  • 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入等待池中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒。(线程池的知识点会在后面讲)
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。(线程同步的知识点会在下个单元讲解)
  • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

实现线程调度

方 法说 明
setPriority(int newPriority)更改线程的优先级
static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
void join()等待该线程终止
static void yield()暂停当前正在执行的线程对象,并执行其他线程

设置线程优先级:

Java线程可以有优先级的设定,高优先级的线程比低优先级的线程有更高的几率得到执行。

  1. 线程的优先级没有指定时,所有线程都携带普通优先级。
  2. 优先级可以用从1到10的范围指定。10表示最高优先级,1表示最低优先级,5是普通优先级。
  3. 记住优先级最高的线程在执行时被给予优先。但是不能保证线程在启动时就进入运行状态。
  4. 与在线程池中等待运行机会的线程相比,当前正在运行的线程可能总是拥有更高的优先级。
  5. 由调度程序决定哪一个线程被执行。
  6. 线程名.setPriority()用来设定线程的优先级。
  7. 记住在线程开始方法被调用之前,线程的优先级应该被设定。
  8. 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY来设定优先级。

线程优先级可参考此博客: Java 多线程:线程优先级

【案例3】 设置线程的优先级

代码实现:

package day18;

//创建Runnable接口实现类
public class Myth03 implements Runnable {
    //重写run方法
    @Override
    public void run() {
        //for循环
        for (int i = 1; i <= 10 ; i++) {
            //输出
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

package day18;

//创建测试类
public class Test03 {
    //程序入口
    public static void main(String[] args) {
//        //方法一:
//        //创建线程类对象
//        Myth03 m3 = new Myth03();
//        //创建Thread类对象
//        Thread t1 = new Thread(m3,"线程一");
//        Thread t2 = new Thread(m3,"线程二");
//        Thread t3 = new Thread(m3,"线程三");
//        //设置最高优先级
//        t1.setPriority(Thread.MAX_PRIORITY);
//        //设置正常优先级,默认情况下即为5
//        t2.setPriority(5);
//        //设置最低优先级
//        t3.setPriority(Thread.MIN_PRIORITY);
//
//        //调用start方法开启线程
//        t1.start();
//        t2.start();
//        t3.start();



        //方法二:
        //创建Thread类对象  Thread类对象中包含线程类对象
        Thread t1=new Thread(new Myth03(),"线程一");
        Thread t2=new Thread(new Myth03(),"线程二");
        Thread t3=new Thread(new Myth03(),"线程三");
        //设置最高优先级
        t1.setPriority(Thread.MAX_PRIORITY);
        //设置正常优先级,默认情况下即为5
        t2.setPriority(5);
        //设置最低优先级
        t3.setPriority(Thread.MIN_PRIORITY);

        //调用start方法开启线程
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

运行结果1

运行结果2
由此可知,设置优先级,只是优先执行的概率变大,但不是绝对的。

线程的强制执行

通过join()方法,使当前线程暂停执行,等待其他线程结束后再继续执行本线程。

【案例4】 线程的强制执行

代码实现:

package day18;

//创建Runnable接口实现类
public class Myth04 implements Runnable {
    //重写run方法
    @Override
    public void run() {
        //for循环
        for (int i = 0; i < 20; i++) {
            //输出
            System.out.println(Thread.currentThread().getName()+"运行:"+i);
        }
    }
}

package day18;

//创建测试类
public class Test04 {
    //程序入口
    public static void main(String[] args) {
        //创建Thread类对象  Thread类对象中包含线程类对象
        Thread t1 = new Thread(new Myth04(),"自定义线程");
        //调用start方法开启线程
        t1.start();

        //for循环
        for( int i = 0; i < 20 ; i++ ){
            //使用if判断当i=6的时候 让自定义线程开始执行 待自定义线程执行完之后再执行主线程
            if(i==6){
                try {
                    //使用join方法让t1加入进来
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //输出
            System.out.println("主线程"+"运行:"+i);
        }
    }
}

运行结果:

运行结果1

运行结果2

线程休眠

运用sleep()方法,使正在执行的线程暂停一段时间,将CPU让给别的线程,即让线程进入休眠等待状态。

线程休眠只是让该线程停一段时间,一段时间之后就可以接着进行,不需要别的线程进行完,例子中因为线程一总执行时间较短,在主线程休眠的时间内就运行完了。

【案例5】 线程的休眠

代码实现:

package day18;

//继承Thread类
public class Myth05 extends Thread {
    //设置线程名称的属性
    private String name;

    //有参构造
    public Myth05(String name) {
        this.name = name;
    }

    //重写run方法
    @Override
    public void run() {
        //for循环
        for (int i = 0; i < 20 ; i++) {
            //输出
            System.out.println(this.name + i );
        }
    }
}

package day18;

//创建测试类
public class Test05 {
    //程序入口
    public static void main(String[] args) throws InterruptedException {
        //创建线程类对象
        Myth05 m1 = new Myth05("熊大");
        //调用start方法开启线程
        m1.start();

        //for循环
        for (int i = 0; i < 10; i++) {
            //使用if判断当i=6的时候 让主线程休眠
            if (i == 6) {
                //进行休眠
                Thread.sleep(500);
            }
            //输出
            System.out.println("熊二" + i );
        }
    }
}

运行结果:

运行结果1

运行结果2
由结果可知,主线程休眠,子线程先执行

线程礼让

通过yield()方法,将当前进程停下,换成就绪状态,让系统的调度器重新调度一次。

与sleep()方法相似,但yield()方法不会阻塞该线程,之后该线程与其他线程是相对公平的。调度谁看系统,有可能还是调度它自己。

【案例6】线程的礼让

代码实现:

package day18;

//创建Runnable接口实现类
public class Myth06 implements Runnable {
    //重写run方法
    @Override
    public void run() {
        //for循环
        for (int i = 0; i < 20 ; i++) {
            //使用if判断当i=5的时候 进行礼让
            if (i==5){
                //输出执行礼让
                System.out.println(Thread.currentThread().getName()+"执行礼让");
                //执行礼让
                Thread.yield();//执行礼让
            }
            //输出
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

package day18;

//创建测试类
public class Test06 {
    //程序入口
    public static void main(String[] args) {
        //创建线程对象
        Myth06 my=new Myth06();
        //创建Thread类对象
        Thread t1=new Thread(my,"线程A");
        Thread t2=new Thread(my,"线程B");
        //调用start方法开启线程
        t1.start();
        t2.start();
    }
}

运行结果:

运行结果1

运行结果2
执行代码可知,线程礼让的动作不一定成功

【综合案例1】使用多线程模拟多人徒步爬山

分析:

1.每个线程代表一个人

2.设置每人爬山速度

3.每爬完100米显示信息

4.爬完了显示信息

实现步骤:

  1. 创建线程类
  2. 构造方法完成属性初始化,实现run()方法
  3. 线程休眠模拟爬山中的延时
  4. 实现测试类

代码实现:

package day18;

//继承Thread类
public class Myth07 extends Thread{
    // 定义爬100米的时间
    private int time;
    // 定义爬多少个100米的数量
    public int num = 0;

    //有参构造
    public Myth07(String name, int time, int kilometer) {
        super(name);
        this.time = time;
        this.num = kilometer * 1000 / 100;
    }

    //重写run方法
    @Override
    public void run() {
        //进行while循环
        while (num > 0) {
            try {
                //进行休眠
                Thread.sleep(this.time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //输出爬完100米
            System.out.println(Thread.currentThread().getName() + "爬完100米!");
            //数量减减
            num--;
        }
        //输出爬完了
        System.out.println(Thread.currentThread().getName()+"爬完了");
    }
}

package day18;

//创建测试类
public class Test07 {
    //程序入口
    public static void main(String[] args) {
        //创建线程类对象
        Myth07 young = new Myth07("年轻人",500,1);
        Myth07 old = new Myth07("老年人",1000,1);

        //调用start方法开启线程
        young.start();
        old.start();
    }
}

运行结果:

运行结果

【综合案例2】某科室一天需看普通号50个,特需号10个,特需号看病时间是普通号的2倍,开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高,当普通号叫完第10号时,要求先看完全部特需号,再看普通号,使用多线程模拟这一过程

实现步骤:

  1. 创建线程类
  2. 构造方法完成属性初始化,实现run()方法
  3. 线程休眠模拟特需号和普通号的时间
  4. 实现测试类

代码实现:

package day18;

//创建Runnable接口实现类
public class Myth08 implements Runnable {
    //重写run方法
    @Override
    public void run() {
        //for循环
        for (int i = 0; i < 10; i++) {
            //输出特需号信息
            System.out.println("特需号:" + (i+1) );
            try {
                //进行休眠
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
package day18;

//创建测试类
public class Test08 {
    //程序入口
    public static void main(String[] args) {
        //创建Thread类对象  Thread类对象中包含线程类对象
        Thread t1 = new Thread(new Myth08());
        //设置最高优先级
        t1.setPriority(Thread.MAX_PRIORITY);
        //调用start开启线程
        t1.start();


        //for循环
        for(int i=0;i<20;i++){
            //输出普通号信息
            System.out.println("普通号:" + (i+1) );
            try {
                //进行休眠
                Thread.sleep(500);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }

            //使用if判断当i=9的时候 让特需号线程开始执行 待特需号线程执行完之后再执行普通号线程
            if(i==9){
                try {
                    //使用join方法让t1加入进来
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果:

运行结果

小结

  1. Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。 实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
  2. 实际开发中建议使用Runnable实现多线程。
  3. 在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。
  4. 线程的礼让和设置线程的优先级,都是让线程获得CPU资源的概率大,但是并不绝对。
  5. 线程的休眠会使其他线程先被CPU分配资源。

以上描述的代码全为我本人亲自敲打,可能会有些错误的地方,如有更好的建议,欢迎您在评论区友善发言。

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

编程在手天下我有

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

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

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

打赏作者

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

抵扣说明:

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

余额充值