多线程概述

1、线程概述

什么是进程?什么是线程?

​ 进程是一个是一个应用程序(1个进程是一个软件)。

​ 线程是一个进程中的执行场景\执行单元。

​ 一个进程可以启动多个线程。

在Java语言中,线程A和线程B,堆内存和方法区共享。但是栈独立,一个线程一个栈。

假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

Java中之所以有多线程机制,是为了提高程序的处理效率。

2、实现线程

Java语言中,实现线程有三种方式:

2.1 继承Thread

​ 第一种:编写一个类,直接继承java.lang.Thread,重写run方法。

public class ThreadTeat02 {
    public static void main(String[] args) {
        //main方法主线程,在住栈中运行
        //新建一个分支线程对象
        MyTread myTread = new MyTread();
        //启动线程
        /*
        start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,
        这段代码任务完成之后,瞬间就结束了。
        启动成功的线程会自动调用run()方法,并且run()方法在分支栈的底部。
        run()方法在分支栈的底部,main()在主栈的底部。run和main是平级的。
         */
        myTread.start();
        //这里的代码还是在主线程中运行
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程----->" + i);
        }

    }
}

class MyTread extends Thread {
    @Override
    public void run() {
        //编写程序,这段程序运行在分支线程中(分支栈)
        for (int i = 0; i < 10; i++) {
            System.out.println("线程分支----->" + i);
        }
    }
}

运行结果:


程序输出结果,有先有后,有多有少。

2.2 实现Runnable接口

第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。

public class ThreadTest03 {
    public static void main(String[] args) {
//        MyRunnable r = new MyRunnable();
//        Thread t = new Thread(r);
        //合并代码
        Thread t = new Thread(new MyRunnable());
        t.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程----->" + i);
        }
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("分支线程---->" + i);
        }
    }
}

运行结果:

在这里插入图片描述
第二种方式实现接口比较常用,因为一个类实现了接口还可以继承别的类。

        //创建线程,采取匿名内部类的方式
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("分支线程----->"+i);
                }
            }
        });
        t.start();

2.3 实现Callable接口

这种方式实现的线程可以获取线程的返回值。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 实现Callable接口
 * 这种方式的优点:可以获取线程的执行结果
 * 这种方式的缺点:效率较低,在获取t线程执行结果的时候,当前线程受阻,效率较低。
 */
public class ThreadTest02 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //第一步:创建一个“未来任务类”对象
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                //线程执行一个任务,执行之后可能会有一个执行结果
                System.out.println("call method begin");
                Thread.sleep(1000 * 5);
                System.out.println("call method over");
                int a = 100;
                int b = 200;
                return a + b;
            }
        });
        //创建线程对象
        Thread t = new Thread(task);
        //启动线程
        t.start();

        //在主线程main方法中获取t线程的返回结果
        Object obj = task.get();
        System.out.println(obj);
    }
}

3、线程的生命周期

image-20210816104945540

获取当前线程对象、获取线程名字、修改线程名字

//获取当前线程
Thread currentThread = Thread.currentThread();
//这个方法出现在main方法中,这个main就是当前线程
//创建线程对象
        MyTread t = new MyTread();
        //设置线程名字
        t.setName("t0");
        //获取线程名字
        System.out.println(t.getName());
        t.start();

运行结果:

image-20210816110648361

当线程没有设置名字的时候,默认的名字有规律

Thread-0

Thread-1

Thread-2

4、sleep()

关于线程的sleep方法:

static void sleep(long millis)

1、静态方法:Thread.sleep(1000);

2、参数是毫秒

3、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用。

​ 这行代码出现在A线程中,A线程就会进入休眠。

​ 这行代码出现在B线程中,B线程就会进入休眠。

 //当前线程休眠5秒
        Thread.sleep(1000*5);
        //5秒后输出 hello
        System.out.println("hello");
public class ThreadTest06 {
    public static void main(String[] args) {
        Thread t = new MyThread06();
        t.setName("t");
        t.start();
        try {
            //在执行的时候还是会转换成:Thread.sleep(1000*5);
            //这段大代码出现在main方法中,main线程休眠
            t.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //5秒后输出
        System.out.println("hello world");
    }
}

class MyThread06 extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("分支线程---->" + i);
        }
    }
}

4.1、终止线程的睡眠

public class ThreadTest07 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread07());
        t.setName("t");
        t.start();
        //希望5秒后中止t线程睡眠
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断t线程睡眠
        //打断
        t.interrupt();
    }
}

class MyThread07 extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---->begin");
        //run方法在父类中没有抛出任何异常
        // 子类的方法不能比父类抛出给多的异常,此处必须使用try..catch
        try {
            //睡眠一年
            Thread.sleep(1000 * 3600 * 24 * 365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //一年后输出
        System.out.println(Thread.currentThread().getName() + "---->over");
    }
}

4.2、怎么合理的终止一个线程的执行

public class ThreadTest08 {
    public static void main(String[] args) {
        MyRunnable08 r = new MyRunnable08();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终止线程
        r.run = false;
    }
}

class MyRunnable08 implements Runnable {
    //标记一个布尔值
    boolean run = true;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            if (run) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            } else {
                return;
            }
        }
    }
}

5、synchronized

什么时候数据在多线程并发的环境下会存在安全问题?

​ 1、多线程并发

​ 2、有共享数据

​ 3、共享数据有修改行为

满足以上三个条件就会存在线程安全问题

怎么解决线程安全问题?

​ 线程排队执行(不能并发)。

用排队执行解决线程安全问题,这种机制被称为:线程同步机制

​ 专业术语叫做,线程同步,实际上就是线程不能并发了,线程必须排队执行。

​ 线程同步就是线程排队,线程排队就会牺牲一部分效率,但是数据安全第一位。

异步编程模型

​ 线程t1和线程t2,各自执行各自的,谁也不需要等谁,也就是多线程并发。效率较高,但是会出现线程安全问题。

同步编程模型

​ 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低,线程排队执行。

package com.wcs.bank;

/**
 * @author wcs
 * @create 2021-08-16 20:02
 */
public class Account {
    private String no;
    double balance;

    public Account(){

    }

    public Account(String no, double balance) {
        this.no = no;
        this.balance = balance;
    }

    public String getNo() {
        return no;
    }

    public void setNo(String no) {
        this.no = no;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public void withdraw(double money){
        double before = this.getBalance();
        double after = before-money;
        this.setBalance(after);
    }
}
package com.wcs.bank;

/**
 * @author wcs
 * @create 2021-08-16 20:11
 */
public class AccountThread extends Thread {
    private Account act;

    public AccountThread(Account act) {
        this.act = act;
    }

    @Override
    public void run() {
        double money = 5000;
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName() + "取款成功,余额:" + act.getBalance());
    }
}
package com.wcs.bank;

/**
 * @author wcs
 * @create 2021-08-16 20:16
 */
public class Test {
    public static void main(String[] args) {
        Account act = new Account("abc-11", 10000);
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);
        t1.setName("t1");
        t2.setName("t2");

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

运行结果:

image-20210816202854377

不使用线程同步机制,多线程对同一个账户取款出现线程安全问题。

要使用线程同步机制解决线程安全问题

线程同步机制的语法是:

	synchronized(){
		//线程同步代码块;
}

synchronized后面小括号中传的这个“数据”必须是多线程共享的数据,才能达到多线程排队。

public void withdraw(double money) {
        synchronized (this) {
            double before = this.getBalance();
            double after = before - money;
            this.setBalance(after);
        }
    }

synochronized有三种写法:

​ 第一种:同步代码块

​ synochronized(线程共享对象){

同步代码块;

}

​ 第二种:在实例方法上使用synochronized

​ 表示共享的对象一定是this,并且同步代码块是整个方法体。

​ 第三种:在静态方法上使用synichronized

​ 表示找类锁

​ 类锁永远只有一把。

6、ReentrantLock

Java在过去很长一段时间只能通过 synchronized 关键字来实现互斥,它有一些缺点。比 如你不能扩展锁之外的方法或者块边界,尝试获取锁时不能中途取消等。Java 5 通过 Lock 接口提供了更复杂的控制来解决这些问题。 ReentrantLock 类实现了 Lock,它拥有与synchronized 相同的并发性和内存语义且它还具有可扩展性。还有一个最主要的就是ReentrantLock还可以实现公平锁机制。什么叫公平锁呢?也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。

package com.wcs;

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

/**
 * ReentrantLock
 */
public class LockTest {
    static Lock lock = new ReentrantLock();

    //true 公平锁 每个线程执行一次
    // static Lock lock = new ReentrantLock(true);
    static void m1() {
        for (int i = 0; i < 5; i++) {
            lock.lock();
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        new Thread(() -> m1(), "t1").start();
        new Thread(() -> m1(), "t2").start();
        new Thread(() -> m1(), "t3").start();
    }
}

7、死锁概述

/**
 * synchronized最好不要嵌套使用
 * 容易发生死锁现象
 */
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new MyThread1(o1, o2);
        Thread t2 = new MyThread1(o1, o2);

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

class MyThread1 extends Thread {
    Object o1;
    Object o2;

    public MyThread1(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o1) {
            synchronized (o2) {

            }
        }
    }
}

class MyThread2 extends Thread {
    Object o1;
    Object o2;

    public MyThread2(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        synchronized (o2) {
            synchronized (o1) {

            }
        }
    }
}

8、开发中应该怎么解决线程安全问题

第一种方案:尽量使用局部变量代替“实例变量和静态变量”。

第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。

第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

9、守护线程

Java语言中线程分为两大类:

​ 一类是:用户线程

​ 一类是:守护线程(后台线程)

​ 其中具有代表性的就是:垃圾回收线程

守护线程的特点:

​ 一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。

注意:主线程main方法是一个用户线程。

/**
 * 守护线程
 */
public class ThreadTest01 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.setName("分支线程");

        //在线程启动之前,将线程设置为守护线程
        t.setDaemon(true);

        t.start();
        //主线程,用户线程
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        int i = 0;
        while (true) {
            System.out.println(Thread.currentThread().getName() + "-->" + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

10、定时器

定时器的作用:

​ 间隔特点的时间,执行特定的程序。

在Java的类库中已经写好了一个定时器:java.util.Timer。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/**
 * 使用定时器指定定时任务
 */
public class TimerTest {
    public static void main(String[] args) throws ParseException {
        //创建定时对象
        Timer timer = new Timer();
        //指定定时任务
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = sdf.parse("2021-8-17 10:20:00");
        //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次)
        timer.schedule(new LogTimerTask(), firstTime, 1000 * 10);

    }
}

class LogTimerTask extends TimerTask {
    @Override
    public void run() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date;
        String strTime = sdf.format(new Date());
        System.out.println(strTime + "成功完成了一次数据备份!");
    }
}

运行结果:

image-20210817102121250

11、关于Object类中的wait和notify方法(生产者和消费者模式)

wait和notify方法不是线程对象的方法,是Java中任何一个Java对象都有的方法,因为这两个是object类中自带的。

wait()方法的作用?

​ Object o = new Object();

​ o.wait();

​ 表示让正在o对象上活动的线程进入等待状态,无限期等待,直到被唤醒为止。

notify方法的作用?

​ Object o = new Object();

​ o.notify();

​ 表示唤醒正在o对象上等待的线程。

还有一个notifyAll()方法:这个方法是唤醒o对象上处于等待的所有线程。

import java.util.ArrayList;
import java.util.List;

/**
 * 使用wait方法和notify方法实现“生产者和消费者模式”
 */
public class ThreadTest03 {
    public static void main(String[] args) {
        // 创建1个仓库对象,共享的。
        List list = new ArrayList();
        // 创建两个线程对象 
        // 生产者线程
        Thread t1 = new Thread(new Producer(list));
        // 消费者线程
        Thread t2 = new Thread(new Consumer(list));

        t1.setName("生产者线程");
        t2.setName("消费者线程");

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

// 生产线程
class Producer implements Runnable {
    // 仓库
    private List list;

    public Producer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        // 一直生产(使用死循环来模拟一直生产)
        while (true) {
            // 给仓库对象list加锁。
            synchronized (list) {
                if (list.size() > 0) { // 大于0,说明仓库中已经有1个元素了。
                    try {
                        // 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到这里说明仓库是空的,可以生产
                Object obj = new Object();
                list.add(obj);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                // 唤醒消费者进行消费
                list.notifyAll();
            }
        }
    }
}

// 消费线程
class Consumer implements Runnable {
    // 仓库
    private List list;

    public Consumer(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        // 一直消费
        while (true) {
            synchronized (list) {
                if (list.size() == 0) {
                    try {
                        // 仓库已经空了。
                        // 消费者线程等待,释放掉list集合的锁
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 程序能够执行到此处说明仓库中有数据,进行消费。
                Object obj = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "--->" + obj);
                // 唤醒生产者生产。
                list.notifyAll();
            }
        }
    }
}

12、多线程工具类

12.1、CountDownLatch

package com.wcs;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * CountDownLatch
 * void await() 使当前线程等待直到锁向下计数为零,除非线程 interrupted。
 * void countDown() 减少锁的数量,释放所有等待的线程,如果计数为零。
 */
public class CountDownLatchTest01 {
    static List<String> list = new ArrayList<>();
    static CountDownLatch latch = new CountDownLatch(1);

    public static void add() {
        System.out.println(Thread.currentThread().getName() + "添加线程启动");
        while (true) {
            try {
                Thread.sleep(1000);
                list.add("hello");
                //添加一个元素size=1,获取下标为1的元素却是第二个元素,所以要-1
                System.out.printf("%s %d%n", list.get(list.size() - 1), list.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (list.size() == 5) {
                //当元素为5个的时候打开门栓
                latch.countDown();
            }
        }
    }

    public static void check() {
        System.out.println(Thread.currentThread().getName() + "监控线程启动");
        try {
            //线程开启后一直在就绪等待,直到门栓打开
            latch.await();
            System.out.println("已经有五个元素了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        //T1线程 添加元素  元素有5个时候,打开门栓告诉T2线程
        new Thread(() -> add(), "T1").start();
        //T2线程 监控元素
        new Thread(() -> check(), "T2").start();
    }
}
package com.wcs;

import java.util.concurrent.CountDownLatch;

/**
 * 多线程启动,有一个线程收尾
 */
public class CountDownLatchTest02 {
    static CountDownLatch latch = new CountDownLatch(5);

    static void work() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        latch.countDown();
    }

    static void over() {
        System.out.println("准备收尾");
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("收尾成功");
    }

    public static void main(String[] args) {
        new Thread(() -> work(), "T1").start();
        new Thread(() -> work(), "T2").start();
        new Thread(() -> work(), "T3").start();
        new Thread(() -> work(), "T4").start();
        new Thread(() -> work(), "T5").start();

        new Thread(() -> over(), "over").start();
    }
}

12.2、CyclicBarrier

package com.wcs;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
 *三人到齐就开饭
 */
public class CyclicBarrierTest {
   static CyclicBarrier bar = new CyclicBarrier(3,()-> System.out.println("大家都到齐了,开饭吧"));

   static void eat(){
        String tn = Thread.currentThread().getName();
        Random rand = new Random();
        int time = rand.nextInt(30);
        System.out.printf("%s 准备动身,预计%d秒后到达%n",tn,time);
        try {
            TimeUnit.SECONDS.sleep(time);
            System.out.printf("%s 到了%n",tn);
            if ("王总".equals(tn)){
                bar.await(5,TimeUnit.SECONDS);
            }
            bar.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
   }

    public static void main(String[] args) {
        new Thread(CyclicBarrierTest::eat,"张三").start();
        new Thread(CyclicBarrierTest::eat,"Jack").start();
        new Thread(()->eat(), "李四").start();
        new Thread(()->eat(), "王总").start();
    }
}

CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程。与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。

13、线程池

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且 一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来 响应处理,它们被称为线程池,里面的线程叫工作线程。从 JDK1.5 开始,Java API 提供 了 Executor 框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目 固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 线程池
 */
public class TestPool {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(5);
        service.execute(new MyThreadPool());
        service.execute(new MyThreadPool());
        service.execute(new MyThreadPool());
        service.execute(new MyThreadPool());

        service.shutdown();
    }
}
class MyThreadPool implements Runnable{

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

都可以用来让一组线程等待其它线程。与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用。

13、线程池

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且 一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来 响应处理,它们被称为线程池,里面的线程叫工作线程。从 JDK1.5 开始,Java API 提供 了 Executor 框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目 固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 线程池
 */
public class TestPool {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(5);
        service.execute(new MyThreadPool());
        service.execute(new MyThreadPool());
        service.execute(new MyThreadPool());
        service.execute(new MyThreadPool());

        service.shutdown();
    }
}
class MyThreadPool implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName());
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值