多线程的学习

进程与线程

进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程——生命周期 比如运行中的QQ。

线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。

并行与并发

并发:多个CPU同时执行多个任务。比如:多个人同时做不同的事。

并行:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

继承Thread类
  • 创建一个新的执行线程有两种方法。 一个是将一个类声明为Thread的子类。 这个子类应该重写run类的方法Thread

1.声明一个去继承Thread类

2.重写run()、实现逻辑

3.创建线程对象

4.开启线程,执行run()

public class Demo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        //输出100以内的偶数
        for (int i = 0; i < 100; i++) {
            if(i%2 == 0){
                System.out.println(i);
            }
        }
    }
}
Thread类的一些方法
//获取当前线程
public static native Thread currentThread();
//获取当前线程名字
public final String getName() 
//设置当前线程名字
public final synchronized void setName(String name) {    
    
//源码:    
 public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
 }    

演示上述方法:

public class Demo2 {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        //自定义当前线程名的方式:通过调用Thread类的setName()进行设置
        //mt.setName("自定义线程");
        //执行start()  1.开启当前线程  2.执行run()
        mt.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        //输出100以内的偶数
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
    //实现类的无参可以省略
    public MyThread(){
        //隐式的初始化Thread的name属性
        super();
    }
}

打印结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m8zUHM3F-1629898426149)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\1629723165769.png)]

static void sleep(long millis)  
正在执行的线程以指定的毫秒数暂停(暂时停止执行),

举例:

public class Demo3 {
    public static void main(String[] args) {
        MyThread2 mt = new MyThread2();
        mt.setName("子线程");
        mt.start();
    }
}
class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //一秒打印一个偶数
            if (i%2 == 0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
void setPriority(int newPriority) 
更改此线程的优先级。 

-- Thread类定义了三种常量类型的优先级

//最小优先级
 public final static int MIN_PRIORITY = 1;
//默认优先级
public final static int NORM_PRIORITY = 5;
//最大优先级
public final static int MAX_PRIORITY = 10;
public class Demo4 {
    public static void main(String[] args) {
        MyThread2 mt = new MyThread2();
        mt.setName("子线程");
        mt.setPriority(Thread.MAX_PRIORITY);
        mt.start();
        for (int i = 0; i < 100; i++) {
            //一秒打印一个偶数
            if (i%2 == 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

class MyThread3 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //一秒打印一个偶数
            if (i%2 == 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
实现Runnable接口
  • 另一种方法来创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。

1.声明Runnable接口的子类

2.重写run(),实现逻辑

3.创建子类对象

4、创建Thread类对象,将子类对象通过构造器传入

public class Demo {
    public static void main(String[] args) {
        MyTread mt = new MyTread();
        Thread t = new Thread(mt);
        t.setName("子线程");
        t.start();
    }
}
class MyTread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

Thread类和Runnable接口的区别?

  1. 实现的方式没有类的单继承性的局限性
  2. 实现的方式更适合来处理多个线程共享数据的情况

实际上Thread类继承了 Runnable接口,本质上没有太大区别,知识一般来处理多个线程共享数据优先选择实现的方式。

public class Thread implements Runnable 
守护线程
  • 守护线程,专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了。
@param  on if {@code true}, marks this thread as a daemon thread
//这句话的意思是如果条件是true 标记这个线程为守护线程

验证:

public class Demo {
    public static void main(String[] args) {
        MyTread mt = new MyTread();
        Thread t = new Thread(mt);
        //设置为守护线程
        t.setDaemon(true);
        t.setName("子线程");
        t.start();
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }

    }
}
class MyTread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0){
                try {
                    //通过睡眠提高线程抢到CPU执行权的概率
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7hOMOCo9-1629898426154)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\1629725701908.png)]

从图中可以看出主线程结束后程序就结束了,这是因为子线程被设置为守护线程。

可以理解为守护线程随着主线程的消亡而消亡。

线程中断

一个线程是一个独立的执行路径,它是否应该结束,应该由自身决定。

public class Demo2 {
    public static void main(String[] args) {
        MyTread2 mt = new MyTread2();
        Thread t = new Thread(mt);
        t.setName("子线程");
        t.start();
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
        //给线程t添加中断标记
        t.interrupt();

    }
}
class MyTread2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i%2 == 0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("发现中断标记,结束该线程");
                    //结束该线程
                    return;
                }
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

打印结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BhY7gwwN-1629898426156)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\1629726546618.png)]

线程安全问题

什么是线程安全问题?

出现问题原因:当某个线程操作车票的过程中,尚未完成,另一个线程也参与进来,对车票进行操作。

public class Demo {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        //创建三个窗口线程进行卖票
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.setName("窗口-1");
        t2.setName("窗口-2");
        t3.setName("窗口-3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Ticket implements Runnable {
    //总票数
    private int ticket = 10;
    @Override
    public void run() {
        while (true) {
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UqTEpDCQ-1629898426161)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\1629727101560.png)]

一共10张票三个窗口进行售出,出现重票和错票的问题。这就是线程安全问题

解决方式:当一个线程a在操作ticket的时候,其他线程不能参与进来。 直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能改变

同步方法

语法: synchronized修饰操作共享数据的方法

public class Demo {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        //创建三个窗口线程进行卖票
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.setName("窗口-1");
        t2.setName("窗口-2");
        t3.setName("窗口-3");
        t1.start();
        t2.start();
        t3.start();
    }
}

class Ticket implements Runnable {
    //总票数
    private int ticket = 10;

    @Override
    public  void run() {
        while (true) {
            sale();
        }
    }
    public synchronized void sale() {
        if (ticket > 0) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
            ticket--;
        }
    }
}
同步代码块

语法:

//锁对象可以是任何的对象 必须是所有线程共享同一把锁对象 
synchronized(锁对象){

	//操作共享数据的代码

}

因为实现Runnable接口只创建了一个子类对象,其他三个线程共用。

public class Demo2 {
    public static void main(String[] args) {
        Windows w = new Windows();
        //创建三个窗口线程进行卖票
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.setName("窗口-1");
        t2.setName("窗口-2");
        t3.setName("窗口-3");
        t1.start();
        t2.start();
        t3.start();
    }
}

class Windows implements Runnable {
    //总票数
    private int ticket = 10;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
Lock锁
//使用ReentrantLock创建锁对象
Lock lock = new ReentrantLock();
//使用多态形式,是因为实现了Lock
public class ReentrantLock implements Lock
public class Demo3 {
    public static void main(String[] args) {
        Windows2 w = new Windows2();
        //创建三个窗口线程进行卖票
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.setName("窗口-1");
        t2.setName("窗口-2");
        t3.setName("窗口-3");
        t1.start();
        t2.start();
        t3.start();
    }
}

class Windows2 implements Runnable {
    //总票数
    private int ticket = 10;
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            //加锁
            lock.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ": 卖票,票号为" + ticket);
                ticket--;
            } else {
                break;
            }
            //释放锁
            lock.unlock();
        }
    }
}

公平锁与非公平锁
//将参数设置为true,表示为公平锁就会执行公平锁策略
Lock lock=new ReentrantLock(true);//公平锁
Lock lock=new ReentrantLock(false);//非公平锁
//也就是线程1首先抢到CPU执行权后,会等到其他两个线程执行完后再操作共享数据。

执行结果应该是下面这样的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ArJCyw2-1629898426165)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\1629728835916.png)]

死锁

何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放

死锁的例子:

public class DeadLockTest {
    public static void main(String[] args) {

        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread(){
            @Override
            public void run() {
                //假设有线程a和线程b  线程一启动,如果
                //线程a拿到了s1锁,这时候睡了一秒 ,阻塞的过程中,很有可能线程b拿到了s2锁,
                //这时候线程b也睡了一秒,想要继续执行下去,必须拿到s1锁  发现s1锁已经被线程a拿到 无法执行下去
                //这时候就出现了 双方都占用这对方需要的共同资源,互相僵持这,出现 死锁。
                synchronized (s1) {
                    s1.append("a");
                    s2.append("1");
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2) {
                        s1.append("b");
                        s2.append("2");
                    }
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2) {
                    s1.append("c");
                    s2.append("3");
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1) {
                        s1.append("d");
                        s2.append("4");
                    }
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }).start();
    }
}

解决方式:同步代码块不要嵌套。

线程通信

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来去走产品。

//店员
class Clerk {
    //产品数量
    private int productCount;

    //生产产品的方法
    public synchronized void producerProduct() {
        if (productCount < 20) {
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
            notify();
        }else {
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    消费产品的方法
    public synchronized void consumerProduct() {
        if (productCount > 0) {
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount--;
            notify();
        }else {
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//消费者
class Consumer extends Thread {
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println("消费者开始消费产品....");
        while (true) {
            try {
                sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumerProduct();
        }
    }
}

//生产者
class Producer extends Thread {
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println("生产者开始生产产品....");
        while (true) {
            try {
                sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.producerProduct();
        }
    }
}

public class ProducerTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer producer = new Producer(clerk);
        Consumer consumer = new Consumer(clerk);
        Consumer consumer2 = new Consumer(clerk);
        producer.setName("生产者1");
        consumer.setName("消费者1");
        consumer2.setName("消费者2");
        producer.start();
        consumer.start();
        consumer2.start();
    }
}

实现Callable接口

1.创建实现Callable接口的实现类
2.完成Callable接口的call()重写
3.创建接口的实现类对象
4.该对象作为参数传递给FutureTask类的构造器, 创建该类对象
5.此对象作为参数传递给Thread类的构造器,创建一个线程
6.调用futureTask的get()返回重写call()的返回值

//1.    创建实现Callable接口的实现类
class MyThread implements Callable<Integer>{
    //2.    完成Callable接口的call()重写
    @Override
    public Integer call() throws Exception {
        //完成100以内的偶数之和
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum+=i;
            }
        }
        return sum;
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3.    创建接口的实现类对象
        MyThread m1 = new MyThread();
        //4.    该对象作为参数传递给FutureTask类的构造器, 创建该类对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(m1);
        //5.    此对象作为参数传递给Thread类的构造器,创建一个线程
        Thread t1 = new Thread(futureTask);
        t1.start();
        try {
            //6.    调用futureTask的get()返回重写call()的返回值
            Integer sum = futureTask.get();
            System.out.println("偶数总和: " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

线程池

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程

就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容

器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

  • 使用线程池的好处:

  • 降低资源消耗。

  • 提高响应速度。

  • 提高线程的可管理性

四种线程池

1.缓存线程池

创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行

很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造

的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并

从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资

		/*** 
         * 缓存线程池.(长度无限制) 
         * 执行流程: 
         * 1. 判断线程池是否存在空闲线程 *
         * 2. 存在则使用 
         * 3. 不存在,则创建线程 并放入线程池, 然后使用 
         */
public class Test3 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        //向线程池中 加入 新的任务 
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
    }
}

2.定长线程池

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大

多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,

则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何

线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之

前,池中的线程将一直存在。

public class Test2 {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        while(true) {
            threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " is running ..");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

3.单线程线程池

Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程

池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去

public class Test4 {
    public static void main(String[] args) {
       /*  效果与定长线程池 创建时传入数值1 效果一致.
         单线程线程池. 
         执行流程: 
         1. 判断线程池 的那个线程 是否空闲 
         2. 空闲则使用 
         3. 不空闲,则等待 池中的单个线程空闲后 使用 */
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程的名称:" + Thread.currentThread().getName());
            }
        });
    }
}

4.周期性任务定长线程池

创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

public class Test {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3);
        /**
         * 定时执行
         * 参数1. runnable类型的任务
         * 参数2. 时长数字 *
         * 参数3. 时长数字的单位 
         */
        scheduledThreadPool.schedule(new Runnable(){
            @Override
            public void run() {
                System.out.println("延迟三秒");
            }
        }, 3, TimeUnit.SECONDS);

        /**
         * 周期执行
         * 参数1. runnable类型的任务
         * 参数2. 时长数字(延迟执行的时长)
         * 参数3. 周期时长(每次执行的间隔时间) 
         * 参数4. 时长数字的单位
         */
        scheduledThreadPool.scheduleAtFixedRate(new Runnable(){
            @Override
            public void run() {
                System.out.println("延迟 1 秒后每三秒执行一次");
            }
        },1,3,TimeUnit.SECONDS);
    }
}

Lambda表达式

jdk1.8中Lambda新特性极大的提高编程效率和程序可读性。

比如使用匿名内部类方式创建线程

 new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i%2 == 0){
                        System.out.println(Thread.currentThread().getName()+":"+i);
                    }
                }
            }
  }).start();

使用Lambda表达式的方式实现:

Lambda表达式语法
左边右边
方法名里面的参数列表方法体内的逻辑
new Thread(() -> System.out.println("Lambda方式创建线程")).start();

##### Lambda表达式

**jdk1.8Lambda新特性极大的提高编程效率和程序可读性。**

比如使用匿名内部类方式创建线程

 new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i%2 == 0){
                        System.out.println(Thread.currentThread().getName()+":"+i);
                    }
                }
            }
  }).start();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值