创建线程的三种方式、线程运行原理、常见方法、线程状态

1.创建线程的三种方式

1. 继承Thread类并重写run方法。
2. 实现Runnable接口,然后实现其run方法。
3. 通过Callable和Future创建线程。

将我们希望线程执行的代码放到 run 方法中,然后通过 start 方法来启动线程,start 方法首先为线程的执行准备 好系统资源,然后再去调用run方法。

1.1 继承Thread类并重写run方法

  • 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体(线程体)。
  • 创建Thread子类的实例,即创建了线程对象。
  • 调用线程对象的start()方法来启动该线程。
  • Java不允许多继承。
/**
 * @ClassName MyThread
 * @author: shouanzh
 * @Description 继承Thread类创建线程类 (extends)
 * @date 2022/3/2 20:26
 */
@Slf4j
public class MyThread extends Thread{

	// 通过构造函数传递参数
	private String name;
    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        // 具体任务逻辑
        System.out.println("继承Thread类创建线程类...");
        log.info("继承Thread类创建线程类...");
    }
}

/**
 * @ClassName CreateThread
 * @author: shouanzh
 * @Description 测试
 * @date 2022/3/2 20:29
 */
public class CreateThread {

    public static void main(String[] args) {

        MyThread myThread = new MyThread("T1");
        myThread.setName("T1");
        
		MyThread myThread2 = new MyThread("T2");
        myThread.setName("T2");
        // 启动线程
        myThread.start();
        myThread2.start();

    }

}

1.2 使用Runnable配合Thread

  • 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  • 创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  • 调用线程对象的start()方法来启动该线程。
  • Runnable接口支持多继承
/**
 * @ClassName RunnableThreadTest
 * @author: shouanzh
 * @Description 使用Runnable配合Thread
 * @date 2022/3/2 20:38
 */
@Slf4j
public class RunnableThreadTest implements Runnable{

	// 通过变量和方法传递数据
	private String name;
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        // 创建线程任务
        System.out.println("继承Thread类创建线程类...");
        log.info("继承Thread类创建线程类...");
    }
}

/**
 * @ClassName CreateThread
 * @author: shouanzh
 * @Description 测试
 * @date 2022/3/2 20:29
 */
public class CreateThread {

    public static void main(String[] args) {

        RunnableThreadTest runnableThread = new RunnableThreadTest();

		// Runnable可以实现多个相同的程序代码的线程去共享同一个资源
        Thread  thread = new Thread(runnableThread);
        Thread  thread2 = new Thread(runnableThread);

        thread.start();
        thread2.start();

    }

}

1.3 通过Callable和FutureTask创建线程

  • 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
  • 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。)
  • 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  • 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
/**
 * @ClassName CallableThreadTest
 * @author: shouanzh
 * @Description 通过Callable和FutureTask创建线程
 * @date 2022/3/2 20:46
 */
public class CallableThreadTest implements Callable<String> {

    @Override
    public String call() throws Exception {
        return "hello word";
    }
}

**
 * @ClassName CreateThread
 * @author: shouanzh
 * @Description 测试
 * @date 2022/3/2 20:29
 */
public class CreateThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CallableThreadTest callableThread = new CallableThreadTest();

        FutureTask<String> futureTask = new FutureTask<>(callableThread);

        Thread thread = new Thread(futureTask,"T1");

        thread.start();

		// 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
        String result = futureTask.get();

        System.out.println(result);

    }

}

2.Runnable和Thread的区别

Runnable与Thread,前者的实现方式是实现其接口即可,后者的实现方式是继承其类。两者实现方式带来最明显的区别就是,由于Java不允许多继承,因此实现了Runnable接口可以再继承其他类,但是Thread明显不可以。

Runnable可以实现多个相同的程序代码的线程去共享同一个资源,而Thread不可以?

以经典的卖票案列:总共只有5张动车票了,有2个窗口在卖。

1. Runnable方式:

/**
 * @ClassName RunnableThreadTest
 * @author: shouanzh
 * @Description 使用Runnable配合Thread
 * @date 2022/3/2 20:38
 */
@Slf4j
public class RunnableThreadTest implements Runnable {
    
    private int ticket = 5;
    
    @Override
    public void run() {
        while (true) {
            System.out.println("Runnable ticket = " + ticket--);
            if (ticket < 0) {
                break;
            }
        }
    }
}

/**
 * @ClassName CreateThread
 * @author: shouanzh
 * @Description 测试
 * @date 2022/3/2 20:29
 */
public class CreateThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        RunnableThreadTest runnableThread = new RunnableThreadTest();

        // Runnable可以实现多个相同的程序代码的线程去共享同一个资源
        Thread  thread = new Thread(runnableThread);
        Thread  thread2 = new Thread(runnableThread);

        thread.start();
        thread2.start();

    }

}

运行结果:

Runnable ticket = 5
Runnable ticket = 3
Runnable ticket = 2
Runnable ticket = 1
Runnable ticket = 0
Runnable ticket = 4

Process finished with exit code 0

一个任务可以启动多个线程,通过Runnable方式实现的线程,实际是开辟一个线程,将任务传递进去,由此线程执行。可以实例化多个 Thread对象,将同一任务传递进去,也就是一个任务可以启动多个线程来执行它。这些线程执行的是同一个任务,所以他们的资源是共享。

2. Thread方式:

**
 * @ClassName MyThread
 * @author: shouanzh
 * @Description 继承Thread类创建线程类 (extends* @date 2022/3/2 20:26
 */
@Slf4j
public class MyThread extends Thread{

    private int ticket = 5;

    @Override
    public void run() {
        while (true) {
            System.out.println("Runnable ticket = " + ticket--);
            if (ticket < 0) {
                break;
            }
        }
    }
}

测试:
方式一:

因为一个线程只能启动一次,通过Thread实现线程时,线程和线程所要执行的任务是捆绑在一起的。也就使得一个任务只能启动一个线程,不同的线程执行的任务是不相同的,所以没有必要,也不能让两个线程共享彼此任务中的资源。

/**
 * @ClassName CreateThread
 * @author: shouanzh
 * @Description 测试
 * @date 2022/3/2 20:29
 */
public class CreateThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 每个线程都独立,不共享资源,每个线程都卖出了5张票,总共卖出了10张。
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        myThread1.start();
        myThread2.start();

    }

}

运行结果

Runnable ticket = 5
Runnable ticket = 4
Runnable ticket = 3
Runnable ticket = 2
Runnable ticket = 1
Runnable ticket = 0
Runnable ticket = 5
Runnable ticket = 4
Runnable ticket = 3
Runnable ticket = 2
Runnable ticket = 1
Runnable ticket = 0

Process finished with exit code 0

方式二:

/**
 * @ClassName CreateThread
 * @author: shouanzh
 * @Description 测试
 * @date 2022/3/2 20:29
 */
public class CreateThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        
        MyThread myThread = new MyThread();

        Thread myThread2 = new Thread(myThread);
        Thread myThread1 = new Thread(myThread);

        myThread1.start();
        myThread2.start();

    }
}

运行结果

Runnable ticket = 5
Runnable ticket = 3
Runnable ticket = 2
Runnable ticket = 1
Runnable ticket = 0
Runnable ticket = 4

Process finished with exit code 0

其中方式一创建了两个MyThread对象,每个对象都有自己的ticket成员变量,当然会多卖。
方式二只创建了一个Thread对象,效果和Runnable一样。
实现Runable只是方便资源共享。当然继承Thrad也可以资源共享。

3.Thread类源代码剖析

  • Thread 类也实现了 Runnable接口,因此实现了Runnable 接口中的run方法。
    在这里插入图片描述
    在这里插入图片描述

  • 构造方法
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 当使用第一种方式来生成线程对象时,我们需要重写 run 方法,因为 Thread 类的run 方法此时什么事情也不做。

  • 当使用第二种方式来生成线程对象时,我们需要实现 Runnable 接口的run 方法,然后使用 new Thread(new MyThread()) MyThread 已经实现了Runnable接口,来生成线程对象,这时的线程对象的run方法 会调用 MyThread 类的run方法,这样我们自己编写的run 方法就执行了。

4.线程运行原理

4.1 栈与栈帧

我们都知道JVM中由堆,栈,方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动 后,虚拟机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

在这里插入图片描述
每个线程下的栈都是私有的,因此每个线程都有自己各自的栈,并且每个栈里面都有很多栈帧,栈帧的大小主要由局部变量表 和 操作数栈决定的

在这里插入图片描述

栈帧debug

/**
 * @ClassName TestFrame
 * @author: shouanzh
 * @Description TestFrame
 * @date 2022/3/2 23:28
 */
public class TestFrame {
    public static void main(String[] args) {
        method1(10);
    }

    public static void method1(int x) {

        int y = x + 1;

        Object object = method2();

        System.out.println(object);

    }

    public static Object method2() {
        Object object = new Object();
        return object;
    }
}

在这里插入图片描述
method2()执行完出栈
在这里插入图片描述
method1()执行完出栈
在这里插入图片描述
结束整个代码的运行
在这里插入图片描述

4.2 线程上下文切换(Thread Context Switch)

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程

  • 线程的 cpu 时间片用完
  • 垃圾回收(STW)
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

当Thread Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

  • 线程的状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

5.Thread的常见方法

在这里插入图片描述
在这里插入图片描述

5.1 调用start 与 run方法的区别

  • 直接调用 run() 是在主线程中执行了 run(),没有启动新的线程
  • 使用 start() 是启动新的线程,通过新的线程间接执行 run()方法中的代码
@Slf4j
public class CreateThread {

    public static void main(String[] args) {

        Thread thread = new Thread("T1"){
            @Override
            public void run() {
                log.info("T1...");
            }
        };

        // 使用 start() 是启动新的线程,通过新的线程间接执行 run()方法中的代码
        thread.start(); // 2022-03-03 20:49:49 [T1] - T1...

        // 直接调用 run() 是在主线程中执行了 run(),没有启动新的线程
        thread.run(); // 2022-03-03 20:49:19 [main] - T1...

    }

}

5.2 sleep 与 yield

在这里插入图片描述
sleep的状态

@Slf4j
public class CreateThread {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread("T1"){
            @SneakyThrows
            @Override
            public void run() {
                Thread.sleep(2000);
                log.info("T1...");
            }
        };

        thread.start();
        // T1的线程先执行了,这时T1的状态为RUNNABLE
        log.info("T1的State:{}",thread.getState()); // T1的State:RUNNABLE

        // 主线程休眠
        Thread.sleep(500);

        log.info("T1的State:{}",thread.getState()); // T1的State:TIMED_WAITING

    }

}

在这里插入图片描述
sleep的打断

@Slf4j
public class CreateThread {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread("T1"){
            @Override
            public void run() {
                try {
                    log.info("T1 enter sleep...");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.info("T1 wake up ...");
                    e.printStackTrace();
                }
            }
        };

        thread.start();

        Thread.sleep(1000);

        log.info("interrupt...");
        thread.interrupt();

    }

}

在这里插入图片描述
TimeUnit

TimeUnit.SECONDS.sleep(1);

yield使cpu调用其它线程,但是cpu可能会再分配时间片给该线程;
而sleep需要等过了休眠时间之后才有可能被分配cpu时间片

在这里插入图片描述

5.3 线程优先级

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它,
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
thread.setPriority(Thread.MAX_PRIORITY);

5.4 join方法详解

等待调用join() 的线程结束。

@Slf4j
public class CreateThread {

    static int r = 0;

    public static void main(String[] args) {

        log.debug("开始");

        Thread t1 = new Thread(() -> {
            log.debug("开始");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("结束");
            r = 10;
        },"t1");

        t1.start();
        t1.join();
        // 这里如果不加t1.join(), 此时主线程不会等待t1线程给r赋值, 主线程直接就输出r=0结束了
        // 如果加上t1.join(), 此时主线程会等待到t1线程执行完才会继续执行.(同步), 此时r=10;
        log.debug("结果为:{}", r);
        log.debug("结束");

    }

}

如果加上t1.join(), 此时主线程会等待到t1线程执行完才会继续执行.(同步), 此时r=10;
在这里插入图片描述
这里如果不加t1.join(), 此时主线程不会等待t1线程给r赋值, 主线程直接就输出r=0结束了
在这里插入图片描述

图解

在这里插入图片描述

下图, 因为开辟了t1线程. 此时程序中有两个线程; main线程和t1线程; 此时在main线程中调用t1.join, 所以main线程只能阻塞等待t1线程执行完. t1线程在1s后将r=10, t1线程执行完, 此时main线程才会接着执行

在这里插入图片描述
有时效的join(long n)

@Slf4j
public class CreateThread {

    static int r = 0;

    public static void main(String[] args) throws InterruptedException {

        log.debug("开始");

        Thread t1 = new Thread(() -> {
            log.debug("开始");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("结束");
            r = 10;
        },"t1");

        t1.start();
        t1.join(1000);
        // 这里如果不加t1.join(), 此时主线程不会等待t1线程给r赋值, 主线程直接就输出r=0结束了
        // 如果加上t1.join(), 此时主线程会等待到t1线程执行完才会继续执行.(同步), 此时r=10;
        log.debug("结果为:{}", r);
        log.debug("结束");

    }

}

在这里插入图片描述

5.5 interrupt 方法详解

该方法用于打断 sleep,wait,join的线程, 在阻塞期间cpu不会分配给时间片

  • 如果是打断因sleep、 wait 、join方法而被阻塞的线程,会将打断标记置为false
  • 如果一个线程在在运行中被打断,打断标记会被置为true

打断sleep的线程,会清空打断状态,以 sleep为例

@Slf4j
public class CreateThread {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            try {
                log.info("sleep...");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");

        t1.start();
        Thread.sleep(500);
        log.debug("111是否被打断?{}",t1.isInterrupted());
        t1.interrupt();
        log.debug("222是否被打断?{}",t1.isInterrupted());
        Thread.sleep(500);
        log.debug("222是否被打断?{}",t1.isInterrupted());
        log.debug("主线程");
    }
}

在这里插入图片描述

打断正常运行的线程
如果一个线程在在运行中被打断,打断标记会被置为true

@Slf4j
public class CreateThread {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while(true) {
                boolean interrupted = Thread.currentThread().isInterrupted();
                if(interrupted) {
                    System.out.println("被打断了, 退出循环");
                    break;
                }
            }
        }, "t1");

        t1.start();
        Thread.sleep(1000);
        System.out.println("interrupt");
        t1.interrupt();
        System.out.println("打断标记为: "+t1.isInterrupted());

    }

}

在这里插入图片描述

5.6 设计模式-两阶段终止模式

在这里插入图片描述
在这里插入图片描述
代码举例:

public class Test7 {
	public static void main(String[] args) throws InterruptedException {
		Monitor monitor = new Monitor();
		monitor.start();
		Thread.sleep(3500);
		monitor.stop();
	}
}

class Monitor {

	Thread monitor;

	/**
	 * 启动监控器线程
	 */
	public void start() {
		//设置线控器线程,用于监控线程状态
		monitor = new Thread() {
			@Override
			public void run() {
				//开始不停的监控
				while (true) {
                    //判断当前线程是否被打断了
					if(Thread.currentThread().isInterrupted()) {
						System.out.println("处理后续任务");
                        //终止线程执行
						break;
					}
					System.out.println("监控器运行中...");
					try {
						//线程休眠
						Thread.sleep(1000); // 情况1 阻塞打断
						System.out.println("记录日志"); // 情况二:正常运行线程被打断
					} catch (InterruptedException e) {
						e.printStackTrace();
						//如果是在休眠的时候被打断,不会将打断标记设置为true,这时要重新设置打断标记
						Thread.currentThread().interrupt();
					}
				}
			}
		};
		monitor.start();
	}

	/**
	 * 	用于停止监控器线程
	 */
	public void stop() {
		//打断线程
		monitor.interrupt();
	}
}

5.7 interrupt 打断 park

@Slf4j
public class CreateThread {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            log.info("park");
            LockSupport.park();
            log.info("uppark");
            log.info("打断状态{}",Thread.currentThread().isInterrupted());
            
            // log.info("打断状态{}",Thread.interrupted());
			// LockSupport.park(); // 打断状态为 true 时 无效 Thread.interrupted() 清除打断标记
			log.info("uppark");
        }, "t1");

        t1.start();
        Thread.sleep(1000);
        System.out.println("interrupt");
        t1.interrupt();
        
    }
}

在这里插入图片描述

5.8 不推荐使用的方法

在这里插入图片描述

5.9 主线程与守护线程

默认情况下, Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其 它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

代码举例

@Slf4j
public class CreateThread {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {
            while (true) {
                // 判断当前线程是否被打断了
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("处理后续任务");
                    //终止线程执行
                    break;
                }
            }
            log.info("结束");
        }, "Daemon");

        t1.setDaemon(true);
        t1.start();

        Thread.sleep(1000);

        log.info("结束");
    }

}

在这里插入图片描述

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

5.10 sleep、yield、wait、join 对比

  • sleep,join,yield,interrupted是Thread类中的方法
  • wait/notify是object中的方法
  • sleep 不释放锁、释放cpu
  • join 释放锁、抢占cpu
  • yiled 不释放锁、释放cpu
  • wait 释放锁、释放cpu

6.线程状态

6.1 操作系统-五种状态

从操作系统方面描述

在这里插入图片描述
在这里插入图片描述

6.2 线程-六种状态

这是从 Java API 层面来描述的
根据Thread.State 枚举,分为六种状态

在这里插入图片描述
在这里插入图片描述

代码举例:

@Slf4j(topic = "c.TestState")
public class TestState {
    public static void main(String[] args) {

        // new 状态 没有调用start()
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        // runnable 状态
        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while(true) {

                }
            }
        };
        t2.start();

        // TERMINATED
        Thread t3 = new Thread("t3") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        t3.start();

        // timed_waiting 显示阻塞状态
        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                synchronized (TestState.class) {
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        // waiting 状态
        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                try {
                	// 等待t2结束。一直等待
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        // blocked 状态
        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (TestState.class) {
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState());
        log.debug("t2 state {}", t2.getState());
        log.debug("t3 state {}", t3.getState());
        log.debug("t4 state {}", t4.getState());
        log.debug("t5 state {}", t5.getState());
        log.debug("t6 state {}", t6.getState());
    }
}

在这里插入图片描述

7.应用之统筹规划

在这里插入图片描述

在这里插入图片描述

代码实现

/**
 * @ClassName Test5
 * @author: shouanzh
 * @Description 应用之统筹
 * @date 2022/3/4 21:50
 */
@Slf4j(topic = "c.Test")
public class Test5 {

    public static void main(String[] args) {

        //  洗水壶 烧水 串行
        Thread t1 = new Thread(() -> {
            log.debug("洗水壶");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            log.debug("烧水");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"王昊");

        //  洗茶壶 洗茶杯 拿茶叶
        Thread t2 = new Thread(() -> {

            log.debug("洗茶壶");

            log.debug("洗茶杯");

            log.debug("拿茶叶");


            try {
                // 等待t1结束
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // t1结束
            log.debug("泡茶");

        },"小赵");

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

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值