Java高级 线程

目录

一、什么是进程

二、什么是线程

三、进程和线程的区别

四、线程的组成

五、线程的组成

六、线程的特点

七、如何创建多线程

7.1 通过继承Thread实现多线程

​编辑 

        7.2 获取和设置线程的名称

        7.3 通过实现Runnable接口完成多线程

        示例一:使用线程Thread类实现4个窗口各卖100张票

        示例二:.实现四个窗口共卖100张票

        示例三:你和你女朋友公用一张银行卡,你向卡中存钱,你女朋友从卡中取钱,使用线程模拟过程!

        八、线程的状态

九、常用的方法

        9.1 练习sleep方法

        9.2 练习yield方法

        9.3 练习join方法

        9.4 练习守护线程方法

        9.5 练习优先级

        十、线程的状态(等待)

        十一、线程的安全问题

        如何解决线程安全问题呢?

        演示线程安全问题:  

            十二、死锁

        演示死锁

   十三、线程通信


一、什么是进程

进程就是正常运行的程序,它是系统分配资源的基本单位。如果在==单核cpu==的情况下,某一时刻只能有一个进程执行,宏观上是并行,微观上是串行。

二、什么是线程

线程是操作系统能够进行运算调度的最小单位;它被包含在进程之中,是进程中的实际运作单位。

多线程,是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。

简单来说:线程是程序中一个单一的顺序控制流程;而多线程就是在单个程序中同时运行多个线程来完成不同的工作。

多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。多线程是在同一时间需要完成多项任务的时候实现的。

多线程有哪些使用场景

并发量大的场景,譬如从一个目录里读入大量文件写入数据库,使用多线程能够极大提高效率

三、进程和线程的区别

四、线程的组成

五、线程的组成

六、线程的特点

七、如何创建多线程

创建线程的方式有三种:

第一种: 继承Thread类并重新run方法

第二种: 实现Runnable接口

第三种: 实现Callable接口

7.1 通过继承Thread实现多线程

//创建一个类并继承Thread
public class MyThread extends Thread {
    //重写run方法----线程运行时,执行的任务体,未来根据需求把线程启动时需要执行的代码放入run方法中
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println("线程执行==========="+i);
        }
    }
}


public class Test {
    //任何一个java程序都包含main主线程
    public static void main(String[] args) {
        //创建一个线程对象
        MyThread my1=new MyThread();
        //2.开启线程 线程获取到cpu时间片后会执行run方法  千万不要调用run方法,如果调用则表示串行化,
        my1.start();

        //这个for循环是主线程的代码
        for (int i = 0; i <20 ; i++) {
            System.out.println("main============="+i);
        }
    }
}

 

        7.2 获取和设置线程的名称

                在Thread类中存在两个方法setName和getName方法

//创建一个类并继承Thread
public class MyThread extends Thread {
    //重写run方法----线程运行时,执行的任务体,未来根据需求把线程启动时需要执行的代码放入run方法中
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println(this.getName()+"执行==========="+i);
        }
    }
}

public class Test {
    //任何一个java程序都包含main主线程
    public static void main(String[] args) {
        //创建一个线程对象
        MyThread my1=new MyThread();
        my1.setName("线程A");
        //2.开启线程 线程获取到cpu时间片后会执行run方法  千万不要调用run方法,如果调用则表示串行化,
        my1.start();
        //创建一个线程对象
        MyThread my2=new MyThread();
        my2.setName("线程B");
        //2.开启线程 线程获取到cpu时间片后会执行run方法  千万不要调用run方法,如果调用则表示串行化,
        my2.start();

        //这个for循环是主线程的代码
        for (int i = 0; i <20 ; i++) {
            System.out.println("main============="+i);
        }
    }
}

        如果没有使用setName方法则jvm会默认为线程起Thread-n名称。  

上面获取和设置线程名称的方法,必须是Thread的子类。 我们在Thread类中提高了一个静态方法,可以获取当前正在执行的线程对象currentThread(). 通过该对象调用getName获取线程名称。  

public class MyThread extends Thread {
    public MyThread(String name){
          super(name);
    }
    //重写run方法----线程运行时,执行的任务体,未来根据需求把线程启动时需要执行的代码放入run方法中
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            //System.out.println(this.getName()+"执行==========="+i);
            System.out.println(Thread.currentThread().getName()+"执行==========="+i);
        }
    }
}

public class Test {
    //任何一个java程序都包含main主线程
    public static void main(String[] args) {
        //创建一个线程对象
        MyThread my1=new MyThread("A");
       // my1.setName("线程A");
        //2.开启线程 线程获取到cpu时间片后会执行run方法  千万不要调用run方法,如果调用则表示串行化,
        my1.start();
        //创建一个线程对象
        MyThread my2=new MyThread("B");
        //my2.setName("线程B");
        //2.开启线程 线程获取到cpu时间片后会执行run方法  千万不要调用run方法,如果调用则表示串行化,
        my2.start();


        //这个for循环是主线程的代码
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"============="+i);
        }
    }
}

        7.3 通过实现Runnable接口完成多线程

public class MyRunnable implements Runnable {
    //该方法 是线程执行时的任务代码块。 线程开启后,抢夺到cpu后会执行该方法
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"正在执行==="+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //0.创建任务对象
        MyRunnable task1=new MyRunnable();
        //1.创建线程对象
        Thread t1=new Thread(task1);
        //开启线程,会执行Runnable中的run方法
        t1.start();
        for (int i = 0; i <20 ; i++) {
            System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~"+i);
        }
    }
}

                上面两种实现线程的方式哪种比较好:  

                                单单从代码的简易度来看---Thread方式比较好,

                                但是从程序的扩展性来说----Runnable比较好

        示例一:使用线程Thread类实现4个窗口各卖100张票

//窗口A卖完100张,B才能卖,B卖完C窗口才能卖,。。。。
public class Ticket extends Thread{
    @Override
    public void run() {
        for(int i=100;i>0;i--){
            System.out.println(Thread.currentThread().getName()+"卖票一张,还剩 "+i+" 张");
        }
    }
}
 
class TestTicket{
    public static void main(String[] args) throws InterruptedException {
        Ticket ticket1 = new Ticket();  //创建线程对象
        ticket1.setName("窗口A"); //设置线程名称---执行时会调用run方法
 
        Ticket ticket2 = new Ticket();
        ticket2.setName("窗口B");
        Ticket ticket3 = new Ticket();
        ticket3.setName("窗口C");
        Ticket ticket4 = new Ticket();
        ticket4.setName("窗口D");
        
        ticket1.start();    //开启线程
        ticket1.join();
        ticket2.start();
        ticket2.join();
        ticket3.start();
        ticket3.join();
        ticket4.start();
    }
}

        示例二:.实现四个窗口共卖100张票


 
public class Ticket implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while(true){
            synchronized (Ticket.class){    
                if(ticket>0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "卖票一张,还剩 " + ticket + " 张");
                }
            }
        }
    }
}
 
//四个人共卖100张票
class TestTicket{
    public static void main(String[] args) {
        //创建任务对象
        Ticket ticket = new Ticket();
        //创建线程对象并执行要执行的任务
        Thread thread1 = new Thread(ticket,"窗口A");
        Thread thread2 = new Thread(ticket,"窗口B");
        Thread thread3 = new Thread(ticket,"窗口C");
        Thread thread4 = new Thread(ticket,"窗口D");
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
 
    }
}

        示例三:你和你女朋友公用一张银行卡,你向卡中存钱,你女朋友从卡中取钱,使用线程模拟过程!


public class TestBank{
    public static void main(String[] args) {
        BankCard card = new BankCard(0);
        //创建任务对象
        SaveMoney s = new SaveMoney(card);
        //创建线程对象并执行要执行的任务
        Thread t1 = new Thread(s,"张三");
 
        DrawMoney d = new DrawMoney(card);
        Thread t2 = new Thread(d,"李四");
 
        t1.start();
        t2.start();
    }
}
class BankCard{
    private double balance;
 
    public BankCard() {
    }
 
    public BankCard(double balance) {
        this.balance = balance;
    }
 
    public double getBalance() {
        return balance;
    }
 
    public void setBalance(double balance) {
        this.balance = balance;
    }
}
//存钱
class SaveMoney implements Runnable{
    private BankCard card;
 
    public SaveMoney() {
    }
 
    public SaveMoney(BankCard card) {
        this.card = card;
    }
 
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            card.setBalance(card.getBalance()+1000);
            System.out.println(Thread.currentThread().getName()+"存钱1000元,卡上余额"+card.getBalance()+"元");
        }
    }
}
//取钱
class DrawMoney implements Runnable{
    private BankCard card;
 
    public DrawMoney() {
    }
 
    public DrawMoney(BankCard card) {
        this.card = card;
    }
 
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            if(card.getBalance()>=1000){
                card.setBalance(card.getBalance()-1000);
                System.out.println(Thread.currentThread().getName()+"取钱1000元,卡上余额"+card.getBalance()+"元");
            }else{
                System.out.println("卡上余额不足");
                i--;
            }
        }
    }
}

        八、线程的状态

九、常用的方法

休眠: public static void sleep(long millis) 当前线程主动休眠millis毫秒。

放弃: public static void yield() 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片

加入: public final void join() 允许其他线程加入到当前线程中,直到其他线程执行完毕后,当前线程才会执行。

优先级: --- 无法验证。 线程对象.setPriority() 线程优先级1-10,默认为5,优先级越高,表示获取CPU的概率越高。

守护线程: 线程对象.setDaemon(true);设置为守护线程。 线程有两类:用户线程(前台线程)和守护线程(后台线程) ==如果程序中所有前台线程都执行完毕了,后台线程也会自动结束。== JVM中垃圾回收线程属于守护线程。

java代码中至少有两个线程---main和垃圾回收线程。

        9.1 练习sleep方法

/**
 * Created by Intellij IDEA
 *
 * @author 王俊凯
 * @Date: 2022/11/1 9:39
 * @Version 1.0
 */
package com.wjk.demo05;

public class Test {
    public static void main(String[] args) {
        SleepDemo sleepDemo=new SleepDemo();
        sleepDemo.start();
    }
}
class SleepDemo extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            try {
                //休眠   当前线程获取cpu后 执行到该处代码时 会休眠一秒 休眠完毕后会继续执行
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"**********"+i);
        }
    }
}

        9.2 练习yield方法

/**
 * Created by Intellij IDEA
 *
 * @author 王俊凯
 * @Date: 2022/11/1 9:42
 * @Version 1.0
 */
package com.wjk.demo06;

import com.wjk.demo04.Boy;

public class YieldDemo extends Thread{

    @Override
    public void run(){
        for (int i = 0; i < 10; i++) {
            //当前线程放弃时间片  ,进入下次抢夺cpu时间片  可能会出现交替的频率比较高
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+"******"+i);
        }
    }
}




/**
 * Created by Intellij IDEA
 *
 * @author 王俊凯
 * @Date: 2022/11/1 9:42
 * @Version 1.0
 */
package com.wjk.demo06;

public class Test06 {
    public static void main(String[] args) {
        YieldDemo y1=new YieldDemo();
        YieldDemo y2=new YieldDemo();
        y1.start();
        y2.start();
    }
}

        9.3 练习join方法

/**
 * Created by Intellij IDEA
 *
 * @author 王俊凯
 * @Date: 2022/11/1 9:46
 * @Version 1.0
 */
package com.wjk.demo07;

public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        JoinDemo j1=new JoinDemo();
        j1.start();
        JoinDemo j2=new JoinDemo();
        j2.start();
        //在main允许下 t1 t2线程执行完毕后才会执行main主线程
        j1.join();
        j2.join();
        for (int i = 0; i < 10; i++) {
            System.out.println("main**********"+i);
        }
    }
}
class JoinDemo extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"********"+i);
        }
    }
}

        9.4 练习守护线程方法

/**
 * Created by Intellij IDEA
 *
 * @author 王俊凯
 * @Date: 2022/11/1 13:18
 * @Version 1.0
 */
package com.wjk.demo09;

public class TestDaemon {
    public static void main(String[] args) {
        Daemon daemon=new Daemon();
        //守护线程 当前台线程执行完毕后 守护线程也会结束
        daemon.setDaemon(true);
        daemon.start();

        Daemon daemon1=new Daemon();
        daemon1.start();

    }
}

class Daemon extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"**************"+i);

        }
    }
}

        9.5 练习优先级

/**
 * Created by Intellij IDEA
 *
 * @author 王俊凯
 * @Date: 2022/11/1 13:07
 * @Version 1.0
 */
package com.wjk.demo08;

public class TestPriority {
    public static void main(String[] args) {
        Priority p1=new Priority("线程A");
        Priority p2=new Priority("线程B");
        Priority p3=new Priority("线程C");
        // 优先级的抢夺  谁的值大  谁抢夺cpu就越快 但不是很确定
        p1.setPriority(1);
        p2.setPriority(6);
        p3.setPriority(10);
        p1.start();
        p2.start();
        p3.start();
    }
}

class Priority extends Thread{

    Priority(String name){
        super(name);
    }


    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"*******"+i);
        }
    }
}

        十、线程的状态(等待)

        十一、线程的安全问题

多个线程对同一个资源进行操作时,就会出现线程安全问题。
比如:我们之前写的买票系统,多个线程窗口对票数进行操作,造成超卖和重卖现象。  写的存钱和取钱,当存入钱后打印余额为0.因为多个线程对同一个银行卡的余额进行操作。

        如何解决线程安全问题呢?

        可以使用锁来完成线程安全问题。java提供了两种锁。
        第一种:自动锁synchronized. 加锁和释放锁都是自动
        第二种:手动锁Lock。  加锁和释放锁都必须手动

        演示线程安全问题:  

package demo14;

import java.util.Arrays;

public class TestSafe {
    private static String [] arr=new String[2];
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {
        //构造函数中可以为线程提供任务对象Runnable
        //匿名实现类
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                if(arr[index]==null){
                     arr[index]="hello";
                     index++;
                }
            }
        });

        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                if(arr[index]==null){
                   
                    arr[index]="world";
                    index++;
                }
            }
        });

        t1.start();
        t2.start();

        //不能直接打印arr,因为main线程可能先获取cpu
        t1.join();
        t2.join();
        System.out.println(Arrays.toString(arr));
    }
}

        第一种使用自动锁synchronized

        语法: 只有代码块执行完毕后,线程才会释放锁资源。其他线程才会参与锁的竞争。

        synchronized(公共锁对象){
                      代码块;
        }

package demo14;

import java.util.Arrays;

public class TestSafe {
    private static String [] arr=new String[2];
    private static int index=0;
    private static Object lock=new Object();
    public static void main(String[] args) throws InterruptedException {
        //构造函数中可以为线程提供任务对象Runnable
        //匿名实现类
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    if (arr[index] == null) {
                        arr[index] = "hello";
                        index++;
                    }
                }
            }
        });

        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    if (arr[index] == null) {
                        arr[index] = "world";
                        index++;
                    }
                }
            }
        });

        t1.start();
        t2.start();

        //不能直接打印arr,因为main线程可能先获取cpu
        t1.join();
        t2.join();
        System.out.println(Arrays.toString(arr));
    }
}

        第二种使用手动锁lock  

        Lock 它是一个接口---它的实现类有三个

        

        * @see ReentrantLock----互斥锁 它的作用和synchronized类似
        * @see Condition ---
        * @see ReadWriteLock-- 读写锁,当进行的为读操作则不会上锁,当进行的为写操作则进行上。

package demo14;

import java.util.Arrays;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestSafe_Lock {
    private static String [] arr=new String[2];
    private static int index=0;
    private static Object lock=new Object();
    public static void main(String[] args) throws InterruptedException {
        //创建锁对象
        Lock lock=new ReentrantLock();
        //构造函数中可以为线程提供任务对象Runnable
        //匿名实现类
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                   try {
                       //加锁
                       lock.lock();
                       if (arr[index] == null) {
                           arr[index] = "hello";
                           index++;
                       }
                   }finally {
                       //释放锁---我们把释放锁徐需要写在finally中
                       lock.unlock();
                   }

            }
        });

        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //加锁
                    lock.lock();
                    if (arr[index] == null) {
                        arr[index] = "world";
                        index++;
                    }
                }finally {
                    //释放锁---我们把释放锁徐需要写在finally中
                    lock.unlock();
                }
            }
        });

        t1.start();

        t2.start();

        //不能直接打印arr,因为main线程可能先获取cpu
        t1.join();
        t2.join();
        System.out.println(Arrays.toString(arr));
    }
}

            十二、死锁

                什么是死锁

        演示死锁

        例子: 有一家情侣餐厅, 餐桌上,只有一双筷子。 男方获取一根筷子,女方获取另一根筷子。

package com.wjk.demo11;

public class Boy extends Thread{

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (Lock.lock1){
            System.out.println(Thread.currentThread().getName()+"获取一个画板");
            synchronized (Lock.lock2){
                System.out.println(Thread.currentThread().getName()+"获取另一个画板");
                System.out.println(Thread.currentThread().getName()+"可以画画了");
            }
        }
    }
}




package com.wjk.demo11;

public class Girl extends Thread{

    @Override
    public void run() {
        synchronized (Lock.lock2){
            System.out.println(Thread.currentThread().getName()+"获取一个画板");
            synchronized (Lock.lock1){
                System.out.println(Thread.currentThread().getName()+"获取另一个画板");
                System.out.println(Thread.currentThread().getName()+"可以画画了");
            }
        }
    }
}



package com.wjk.demo11;

public class Lock {
    public static Object lock1=new Object();
    public static Object lock2=new Object();


}



package com.wjk.demo11;

/**
 * 死锁
 */
public class TestDead {
    public static void main(String[] args) {
        Boy boy=new Boy();
        boy.setName("王俊凯");

        Girl girl=new Girl();
        girl.setName("刘晨晨");


        boy.start();
        girl.start();
    }
}

        如何解决死锁问题:

        1. 尽量不要使用锁嵌套。
        2. 多个线程不要使用同一把锁。
        3. 使用安全类---java.util.concurrent

   十三、线程通信

        我们无法决定哪个线程先获取cpu。 我们可以让线程之间进行通信。我们就可以决定让线程交替执行。

        男女存钱和取钱---->使用线程通信就可以完成存钱和取钱之间的交替运行。

        比如: 对象变量a进行+1 和 -1 交替运行执行10次 使用多线程。

        线程通信需要使用Object类中的方法:

        wait()方法 等待---释放锁资源

        notify()方法 唤醒等待的线程--

        上面的两个方法必须在同步代码块【syn】中才能被调用。

                例子: 要求: 存钱和取钱交替执行。  

        Boy的代码

/**
 * Created by Intellij IDEA
 *
 * @author 王俊凯
 * @Date: 2022/11/1 14:07
 * @Version 1.0
 */
package com.wjk.demo12;

public class Boy extends Thread{
    private BankCard bankCard;
    public Boy(BankCard bankCard){
        this.bankCard=bankCard;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            bankCard.save(999);
        }
    }
}

        Gril的代码

/**
 * Created by Intellij IDEA
 *
 * @author 王俊凯
 * @Date: 2022/11/1 14:07
 * @Version 1.0
 */
package com.wjk.demo12;

public class Girl extends Thread{
    private BankCard bankCard;
    public Girl(BankCard bankCard){
        this.bankCard=bankCard;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            bankCard.token(999);
        }
    }
}

        线程通信的代码

/**
 * Created by Intellij IDEA
 *
 * @author 王俊凯
 * @Date: 2022/10/31 13:10
 * @Version 1.0
 */
package com.wjk.demo12;
@SuppressWarnings("all")
public class BankCard {
    private double balance;
    private boolean flag=false; //如果等于true表示有钱  flase表示没钱

    //存钱方法  synchronized  在方法上加synchronized  表示整个方法都上锁了  默认的锁对象就是this
    public synchronized void save(double money){
        if (flag==true){
            try {
                //表示当前线程释放锁并进入等待队列
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        balance=balance+money;
        System.out.println(Thread.currentThread().getName()+"存入了"+money+"元,卡内余额"+balance);
        flag=true;
        //唤醒等待队列中的线程
        notify();
    }

    //取钱方法
    public synchronized void token(double money){
        if (flag==false){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        balance=balance-money;
        System.out.println(Thread.currentThread().getName()+"取出"+money+"元,卡内余额"+balance);
        flag=false;
        notify();
    }
}

        测试

 

/**
 * Created by Intellij IDEA
 *
 * @author 王俊凯
 * @Date: 2022/11/1 14:09
 * @Version 1.0
 */
package com.wjk.demo12;

/**
 * 线程的通信
 */
public class Test12 {
    public static void main(String[] args) {
        BankCard bankCard=new BankCard();
        Boy boy=new Boy(bankCard);
        boy.setName("王俊凯");
        Girl girl=new Girl(bankCard);
        girl.setName("刘晨晨");
        boy.start();
        girl.start();
    }
}

    

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值