线程的总结

简介 

 

  

线程

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪阻塞运行三种基本状态。每一个程序

  都至少有一个线程,那就是程序本身。

  线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程

编辑本段线程的属性

  在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。

  1)轻型实体

  线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源,比如,在每个线程中都应具有一个用于控制线程运行的线程控制块TCB,用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。

  2)独立调度和分派的基本单位。

  在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小。

  3)可并发执行。

  在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行。

  4)共享进程资源。

  在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。

编辑本段线程与进程

  

  

线程

线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定。线程的运行中需要使用计算机的内存资源和CPU。

  通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。

  因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。

  线程与进程的区别可以归纳为以下几点:

  1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。

  2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。

  3)调度和切换:线程上下文切换比进程上下文切换要快得多。

  4)在多线程OS中,进程不是一个可执行的实体。

编辑本段周期

  

  

线程

1.新建 2.就绪 3.运行 4.阻塞 5.死亡

编辑本段线程调度与优先级

  有线程进入了就绪状态,需要有线程调度程序来决定何时执行,根据优先级来调度。

编辑本段线程组

  每个线程都是一个线程组的一个成员,线程组把多个线程集成一个对象,通过线程组可以同时对其中的多个线程进行操作。在生成线程时必须将线程放在指定的线程组,也可以放在缺省的线程组中,缺省的就是生成该线程的线程所在的线程组。一旦一个线程加入了某个线程组,不能被移出这个组。

编辑本段守护线程

  守护线程是特殊的线程,一般用于在后台为其他线程提供服务.

  Java中,isDaemon():判断一个线程是否为守护线程.

  Java中,set Daemon():设置一个线程为守护线程.

编辑本段线程的好处

  引入线程的好处:

  

  

线程

1 创建一个新线程花费的时间少。

  2 两个线程的切换时间少。

  3 由于同一个进程内的线程共享内存和文件,所以线程之间互相通信必须调用内核。

  4 线程能独立执行,能充分利用和发挥处理机与外围设备并行工作的能力。

编辑本段Java线程同步的方法

  线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源、什么时候需要考虑同步,怎么同步等等问题,当然,这些问题没有很明确的答案,但有些原则问题需要考虑,是否有竞争资源被同时改动的问题?对于同步,在具体的Java代码中需要完成以下两个操作:把竞争访问的资源标识为private;同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。当然这不是唯一控制并发安全的途径。synchronized关键字使用说明synchronized只能标记非抽象的方法,不能标识成员变量。为了演示同步方法的使用,构建了一个信用卡账户,起初信用额为100w,然后模拟透支、存款等多个操作。显然银行账户User对象是个竞争资源,而多个并发操作的是账户方法oper(int x),当然应该在此方法上加上同步,并将账户的余额设为私有变量,禁止直接访问。/** * Java线程:线程的同步

  *

  * @author leizhimin 2009-11-4 11:23:32

  */

  public class Test {

  public static void main(String[] args) {

  User u = new User("张三", 100);

  MyThread t1 = new MyThread("线程A", u, 20);

  MyThread t2 = new MyThread("线程B", u, -60);

  MyThread t3 = new MyThread("线程C", u, -80);

  MyThread t4 = new MyThread("线程D", u, -30);

  MyThread t5 = new MyThread("线程E", u, 32);

  MyThread t6 = new MyThread("线程F", u, 21);

  t1.start();

  t2.start();

  t3.start();

  t4.start();

  t5.start();

  t6.start();

  }

  }

  class MyThread extends Thread {

  private User u;

  private int y = 0;

  MyThread(String name, User u, int y) {

  super(name);

  this.u = u;

  this.y = y;

  }

  public void run() {

  u.oper(y);

  }

  }

  class User {

  private String code;

  private int cash;

  User(String code, int cash) {

  this.code = code;

  this.cash = cash;

  }

  public String getCode() {

  return code;

  }

  public void setCode(String code) {

  this.code = code;

  }

  /**

  * 业务方法

  * @param x 添加x万元

  */

  public synchronized void oper(int x) {

  try {

  Thread.sleep(10L);

  this.cash += x;

  System.out.println(Thread.currentThread().getName() + "运行结束,增加“" + x + "”,当前用户账户余额为:" + cash);

  Thread.sleep(10L);

  } catch (InterruptedException e) {

  e.printStackTrace();

  }

  }

  @Override

  public String toString() {

  return "User{" +

  "code='" + code + '\'' +

  ", cash=" + cash +

  '}';

  }

  }输出结果:线程A运行结束,增加“20”,当前用户账户余额为:120

  线程F运行结束,增加“21”,当前用户账户余额为:141

  线程E运行结束,增加“32”,当前用户账户余额为:173

  线程C运行结束,增加“-80”,当前用户账户余额为:93

  线程B运行结束,增加“-60”,当前用户账户余额为:33

  线程D运行结束,增加“-30”,当前用户账户余额为:3

  Process finished with exit code 0反面教材,不同步的情况,也就是去掉oper(int x)方法的synchronized修饰符,然后运行程序,结果如下:线程A运行结束,增加“20”,当前用户账户余额为:61

  线程D运行结束,增加“-30”,当前用户账户余额为:63

  线程B运行结束,增加“-60”,当前用户账户余额为:3

  线程F运行结束,增加“21”,当前用户账户余额为:61

  线程E运行结束,增加“32”,当前用户账户余额为:93

  线程C运行结束,增加“-80”,当前用户账户余额为:61

  Process finished with exit code 0很显然,上面的结果是错误的,导致错误的原因是多个线程并发访问了竞争资源u,并对u的属性做了改动。可见同步的重要性。注意:通过前文可知,线程退出同步方法时将释放掉方法所属对象的锁,但还应该注意的是,同步方法中还可以使用特定的方法对线程进行调度。这些方法来自于java.lang.Object类。void notify()

  唤醒在此对象监视器上等待的单个线程。

  void notifyAll()

  唤醒在此对象监视器上等待的所有线程。

  void wait()

  导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。

  void wait(long timeout)

  导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量。

  void wait(long timeout, int nanos)

  导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量

编辑本段工作原理

  线程是进程中的实体,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程不拥有系统资源,只有运行必须的一些数据结构;它与父进程的其它线程共享该进程所拥有的全部资源。线程可以创建和撤消线程,从而实现程序的并发执行。一般,线程具有就绪、阻塞和运行三种基本状态。

  在多中央处理器的系统里,不同线程可以同时在不同的中央处理器上运行,甚至当它们属于同一个进程时也是如此。大多数支持多处理器的操作系统都提供编程接口来让进程可以控制自己的线程与各处理器之间的关联度(affinity)。

  有时候,线程也称作轻量级进程。就象进程一样,线程在程序中是独立的、并发的执行路径,每个线程有它自己的堆栈、自己的程序计数器和自己的局部变量。但是,与分隔的进程相比,进程中的线程之间的隔离程度要小。它们共享内存、文件句柄和其它每个进程应有的状态。

  进程可以支持多个线程,它们看似同时执行,但互相之间并不同步。一个进程中的多个线程共享相同的内存地址空间,这就意味着它们可以访问相同的变量和对象,而且它们从同一堆中分配对象。尽管这让线程之间共享信息变得更容易,但您必须小心,确保它们不会妨碍同一进程里的其它线程。

  Java 线程工具和 API 看似简单。但是,编写有效使用线程的复杂程序并不十分容易。因为有多个线程共存在相同的内存空间中并共享相同的变量,所以您必须小心,确保您的线程不会互相干扰。

编辑本段线程的概念

  一般来说,我们把正在计算机中执行的程序叫做"进程"(Process) ,而不将其称为程序(Program)。所谓"线程"(Thread),是"进程"中某个单一顺序的控制流。 新兴的操作系统,如Mac,Windows NT,Windows 95等,大多采用多线程的概念,把线程视为基本执行单位。线程也是Java中的相当重要的组成部分之一。

  甚至最简单的Applet也是由多个线程来完成的。在Java中,任何一个Applet的paint()和update()方法都是由AWT(Abstract Window Toolkit)绘图与事件处理线程调用的,而Applet 主要的里程碑方法——init(),start(),stop()和destory() ——是由执行该Applet的应用调用的。

  单线程的概念没有什么新的地方,真正有趣的是在一个程序中同时使用多个线程来完成不同的任务。某些地方用轻量进程(Lightweig ht Process)来代替线程,线程与真正进程的相似性在于它们都是单一顺序控制流。然而线程被认为轻量是由于它运行于整个程序的上下文内,能使用整个程序共有的资源和程序环境。

  作为单一顺序控制流,在运行的程序内线程必须拥有一些资源作为必要的开销。例如,必须有执行堆栈和程序计数器。在线程内执行的代码只在它的上下文中起作用,因此某些地方用"执行上下文"来代替"线程"。

编辑本段线程属性

  为了正确有效地使用线程,必须理解线程的各个方面并了解Java 实时系统。必须知道如何提供线程体、线程的生命周期、实时系统如 何调度线程、线程组、什么是幽灵线程(Demo nThread)。

(1)线程体

  所有的操作都发生在线程体中,在Java中线程体是从Thread继承的run()方法,或实现Runnable接口的类中的run()方法。当线程产生并初始化后,实时系统调用它的run()方法。run()方法内的代码实现所产生线程的行为,它是线程的主要部分。

(2)线程状态

  附图表示了线程在它的生命周期内的任何时刻所能处的状态以及引起状态改变的方法。这图并不是完整的有限状态图,但基本概括了线程中比较感兴趣和普遍的方面。以下讨论有关线程生命周期以此为据。

  ●新线程态(New Thread)

  产生一个Thread对象就生成一个新线程。当线程处于"新线程"状态时,仅仅是一个空线程对象,它还没有分配到系统资源。因此只能启动或终止它。任何其他操作都会引发异常。

  ●可运行态(Runnable)

  start()方法产生运行线程所必须的资源,调度线程执行,并且调用线程的run()方法。在这时线程处于可运行态。该状态不称为运行态是因为这时的线程并不总是一直占用处理机。特别是对于只有一个处理机的PC而言,任何时刻只能有一个处于可运行态的线程占用处理 机。Java通过调度来实现多线程对处理机的共享。

  ●非运行态(Not Runnable)

  当以下事件发生时,线程进入非运行态。

  ①suspend()方法被调用;

  ②sleep()方法被调用;

  ③线程使用wait()来等待条件变量;

  ④线程处于I/O等待。

  ●死亡态(Dead)

  当run()方法返回,或别的线程调用stop()方法,线程进入死亡态 。通常Applet使用它的stop()方法来终止它产生的所有线程。

(3)线程优先级

  虽然我们说线程是并发运行的。然而事实常常并非如此。正如前面谈到的,当系统中只有一个CPU时,以某种顺序在单CPU情况下执行多线程被称为调度(scheduling)。Java采用的是一种简单、固定的调度法,即固定优先级调度。这种算法是根据处于可运行态线程的相对优先级来实行调度。当线程产生时,它继承原线程的优先级。在需要时可对优先级进行修改。在任何时刻,如果有多条线程等待运行,系统选择优先级最高的可运行线程运行。只有当它停止、自动放弃、或由于某种原因成为非运行态低优先级的线程才能运行。如果两个线程具有相同的优先级,它们将被交替地运行。 Java实时系统的线程调度算法还是强制性的,在任何时刻,如果一个比其他线程优先级都高的线程的状态变为可运行态,实时系统将选择该线程来运行。

(4)幽灵线程

  任何一个Java线程都能成为幽灵线程。它是作为运行于同一个进程内的对象和线程的服务提供者。例如,HotJava浏览器有一个称为" 后台图片阅读器"的幽灵线程,它为需要图片的对象和线程从文件系统或网络读入图片。 幽灵线程是应用中典型的独立线程。它为同一应用中的其他对象和线程提供服务。幽灵线程的run()方法一般都是无限循环,等待服务请求。

(5)线程组

  每个Java线程都是某个线程组的成员。线程组提供一种机制,使得多个线程集于一个对象内,能对它们实行整体操作。譬如,你能用一个方法调用来启动或挂起组内的所有线程。Java线程组由ThreadGroup类实现。

  当线程产生时,可以指定线程组或由实时系统将其放入某个缺省的线程组内。线程只能属于一个线程组,并且当线程产生后不能改变它所属的线程组。

编辑本段多线程程序

  对于多线程的好处这就不多说了。但是,它同样也带来了某些新的麻烦。只要在设计程序时特别小心留意,克服这些麻烦并不算太困难。

(1)同步线程

  许多线程在执行中必须考虑与其他线程之间共享数据或协调执行状态。这就需要同步机制。在Java中每个对象都有一把锁与之对应。但Java不提供单独的lock和unlock操作。它由高层的结构隐式实现, 来保证操作的对应。(然而,我们注意到Java虚拟机提供单独的monito renter和monitorexit指令来实现lock和unlo

  ck操作。) synchronized语句计算一个对象引用,试图对该对象完成锁操作, 并且在完成锁操作前停止处理。当锁操作完成synchronized语句体得到执行。当语句体执行完毕(无论正常或异常),解锁操作自动完成。作为面向对象的语言,synchronized经常与方法连用。一种比较好的办法是,如果某个变量由一个线程赋值并由别的线程引用或赋值,那么所有对该变量的访问都必须在某个synchromized语句或synchronized方法内。

  现在假设一种情况:线程1与线程2都要访问某个数据区,并且要求线程1的访问先于线程2, 则这时仅用synchronized是不能解决问题的。这在Unix或Windows NT中可用Simaphore来实现。而Java并不提供。在Java中提供的是wait()和notify()机制。使用如下:

  synchronized method-1(…){ call by thread 1.

  ∥access data area;

  available=true;

  notify()

  }

  synchronized method-2(…){∥call by thread 2.

  while(!available)

  try{

  wait();∥wait for notify().

  }catch (Interrupted Exception e){

  }

  ∥access data area

  }

  其中available是类成员变量,置初值为false。

  如果在method-2中检查available为假,则调用wait()。wait()的作用是使线程2进入非运行态,并且解锁。在这种情况下,method-1可以被线程1调用。当执行notify()后。线程2由非运行态转变为可运行态。当method-1调用返回后。线程2可重新对该对象加锁,加锁成功后执行wait()返回后的指令。这种机制也能适用于其他更复杂的情况。

(2)死锁

  如果程序中有几个竞争资源的并发线程,那么保证均衡是很重要的。系统均衡是指每个线程在执行过程中都能充分访问有限的资源。系统中没有饿死和死锁的线程。Java并不提供对死锁的检测机制。对大多数的Java程序员来说防止死锁是一种较好的选择。最简单的防止死锁的方法是对竞争的资源引入序号,如果一个线程需要几个资源,那么它必须先得到小序号的资源,再申请大序号的资源。

编辑本段比较

  进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。

  另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。

  与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。

  当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。

  线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表TCB组成。寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量。

  发生进程切换与发生线程切换时相比较,进程切换时涉及到有关资源指针的保存以及地址空间的变化等问题;线程切换时,由于同不进程内的线程共享资源和地址 空间,将不涉及资源信息的保存和地址变化问题,从而减少了操作系统的开销时间。而且,进程的调度与切换都是由操作系统内核完成,而线程则既可由操作系统内 核完成,也可由用户程序进行。 图1 多线程与进程之间的关系

编辑本段适用范围

  典型的应用

  1.服务器中的文件管理或通信控制

  2.前后台处理

  3.异步处理

编辑本段执行特性

  一个线程必须处于如下四种可能的??一个线程调用了new方法之后,并在调用start方法之前的所处状态。在初始态中,可以调用start和stop方法。

  Runnable:一旦线程调用了start 方法,线程就转到Runnable 状态,注意,如果线程处于Runnable状态,它也有可能不在运行,这是因为还有优先级和调度问题。

  阻塞/ NonRunnable:线程处于阻塞/NonRunnable状态,这是由两种可能性造成的:要么是因挂起而暂停的,要么是由于某些原因而阻塞的,例如包括等待IO请求的完成。 退出:线程转到退出状态,这有两种可能性,要么是run方法执行结束,要么是调用了stop方法。

  最后一个概念就是线程的优先级,线程可以设定优先级,高优先级的线程可以安排在低优先级线程之前完成。一个应用程序可以通过使用线程中的方法setPriority(int),来设置线程的优先级大小。

  线程有5种基本操作:

  派生:线程在进程内派生出来,它即可由进程派生,也可由线程派生。

  阻塞(Block):如果一个线程在执行过程中需要等待某个事件发生,则被阻塞。

  激活(unblock):如果阻塞线程的事件发生,则该线程被激活并进入就绪队列。

  调度(schedule):选择一个就绪线程进入执行状态。

  结束(Finish):如果一个线程执行结束,它的寄存器上下文以及堆栈内容等将被释放。

  图2 线程的状态与操作

  线程的另一个执行特性是同步。线程中所使用的同步控制机制与进程中所使用的同步控制机制相同。

编辑本段分类

  线程有两个基本类型

  用户级线程:管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。

  系统级线程(核心级线程):由操作系统内核进行管理。操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使用户程序可以创建、执行、撤消线程。

编辑本段举例

  1. SUN Solaris 2.3

  Solaris支持内核线程、轻权进程和用户线程。一个进程可有大量用户线程;大量用户线程复用少量的轻权进程,轻权进程与内核线程一一对应。

  用户级线程在调用核心服务时(如文件读写),需要“捆绑(bound)”在一个LWP上。永久捆绑(一个LWP固定被一个用户级线程占用,该LWP移到LWP池之外)和临时捆绑(从LWP池中临时分配一个未被占用的LWP)。

  在调用系统服务时,如果所有LWP已被其他用户级线程所占用(捆绑),则该线程阻塞直到有可用的LWP。

  如果LWP执行系统线程时阻塞(如read()调用),则当前捆绑在LWP上的用户级线程也阻塞。

  图3 用户线程、轻权进程和核心线程的关系

  ¨ 有关的C库函数

  /* 创建用户级线程 */

  int thr_create(void *stack_base, size_t stack_size,

  void *(*start_routine)(void *), void *arg, long flags,

  thread_t *new_thread_id);

  其中flags包括:THR_BOUND(永久捆绑), THR_NEW_LWP(创建新LWP放入LWP池),若两者同时指定则创建两个新LWP,一个永久捆绑而另一个放入LWP池。

  ² 有关的系统调用

  /* 在当前进程中创建LWP */

  int _lwp_create(ucontext_t *contextp, unsigned long flags,

  lwpid_t *new_lwp_id);

  /* 构造LWP上下文 */

  void _lwp_makecontext(ucontext_t *ucp,

  void (*start_routine)( void *), void *arg,

  void *private, caddr_t stack_base, size_t stack_size);

  /* 注意:没有进行“捆绑”操作的系统调用 */

  2. Windows NT

  NT线程的上下文包括:寄存器、核心栈、线程环境块和用户栈。

  NT线程状态

  (1) 就绪状态:进程已获得除处理机外的所需资源,等待执行。

  (2) 备用状态:特定处理器的执行对象,系统中每个处理器上只能有一个处于备用状态的线程。

  (3) 运行状态:完成描述表切换,线程进入运行状态,直到内核抢先、时间片用完、线程终止或进行等待状态。

  (4) 等待状态:线程等待对象句柄,以同步它的执行。等待结束时,根据优先级进入运行、就绪状态。

  (5) 转换状态:线程在准备执行而其内核堆栈处于外存时,线程进入转换状态;当其内核堆栈调回内存,线程进入就绪状态。

  (6) 终止状态:线程执行完就进入终止状态;如执行体有一指向线程对象的指针,可将线程对象重新初始化,并再次使用。

  图4 Windows NT的线程状态

  NT线程的有关API

  CreateThread()函数??定的函数;返回值为所创建线程的句柄。

  ExitThread()函数用于结束本线程。

  SuspendThread()函数用于挂起指定的线程。

  ResumeThread()函数递减指定线程的挂起计数,挂起计数为0时,线程恢复执行。

线程的组成

  1)一组代表处理器状态的CPU寄存器中的内容

  2)两个栈,一个用于当线程在内核模式下执行的时候,另一个用于线程在用户模式下执行的时候

  3)一个被称为线程局部存储器(TLS,thread-local storage)的私有储存区域,各个子系统、运行库和DLL都会用到该储存区域

  4)一个被称为线程ID(thread ID,线程标识符)的唯一标识符(在内部也被称为客户ID——进程ID和线程ID是在同一个名字空间中生产的,所以它们永远 不会重叠)

  5)有时候线程也有它们自己的安全环境,如果多线程服务器应用程序要模仿其客户的安全环境,则往往可以利用线程的安全环境。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值