Java高级特性(一)多线程

1.Java多线程编程

传统dos系统都是单线程
windows时代才开始多线程设计, 单核情况下,一个时间段内会有多个进程执行,但在一个时间点只有一个进程运行,
线程是在基础上划分的更小的运算单元,线程是在进程的基础上创建的,线程的启动速度比进程快很多,所以多线程进行并发处理时执行速度更快,

继承thread类实现多线程(java.lang.thread)

在这里插入图片描述

package demo;

class mythread01 extends Thread{

    String tite;
    public mythread01(String tite){
        this.tite=tite;
    }
    @Override
    public void run() {
        for(int i=0;i<=10;i++){
            System.out.println(this.tite+"运行,x="+i);
        }
    }
}
public class mythread {
    public static void main(String[] args) {
        new mythread01("线程A").start();
        new mythread01("线程B").start();
        new mythread01("线程C").start();
    }
}

多线程要执行的方法都应该在run()方法中进行定义
要想启动多线程必须使用start()方法完成

new mythread(“线程A”).start()
new mythread(“线程A”).run();

问为什么不使用run()方法,而是使用start()方法,

答:多个线程同时start()时,线程会交替执行,
多个线程同时run()时,线程不会交替执行,会按书写顺序执行。

Java程序中继承永远是单继承的,但是又提供了第二种多线程的主体定义结构形式:实现定义runnable接口,(thread是一个线程类,有start()方法学和run()方法,runnable是一个线程接口,只有run()方法)可以使用public
thread(runnable target)构造方法构造,然后使用thread.start()方法启动,

即优先使用runnable实现多线程,使用thread启动多线程

package demo;

class mythread01 extends Thread implements Runnable{

    String tite;
    public mythread01(String tite){
        this.tite=tite;
    }
    @Override
    public void run() {
        for(int i=0;i<=10;i++){
            System.out.println(this.tite+"运行,x="+i);
        }
    }
}
public class mythread {
    public static void main(String[] args) {
     Thread threadA=new Thread(new mythread01("线程对象A"));
     Thread threadB=new Thread(new mythread01("线程对象B"));
     Thread threadC=new Thread(new mythread01("线程对象C"));
     threadA.start();  //启动线程
     threadB.start();
     threadC.start();
    }
}

在这里插入图片描述
在这里插入图片描述
多线程实现买票的流程

package demo;

class  mythread01 implements Runnable{ //线程主体类
    private int ticket=5;
    @Override
    public void run() {
        for(int x=0;x<200;x++){
            if(this.ticket>0){
                System.out.println("买票,ticket="+ticket--);
            }
        }
    }
}
public class mythread {
    public static void main(String[] args) {
    mythread01 mt=new mythread01();
    new Thread(mt).start();
    new Thread(mt).start();
    new Thread(mt).start();
    }
}

通过内存分析图分析,
在这里插入图片描述
使用runnable访问同一资源,而不是使用thread(当然thread也能够实现),但考虑到单一继承的问题还是使用runnable,

callable(java.util.concurrent.callable)实现多线程

在这里插入图片描述

runnable有一个明显的缺点就是run()方法是没有返回值的,

在这里插入图片描述
实现callable接口调用call()方法获取返回值

callable实现多线程

package demo;

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

class  mythread01 implements Callable { //线程主体类
    private int ticket=5;
    @Override
    public String call() {
        for(int x=0;x<10;x++){
           System.out.println("***线程执行,x="+x);
        }
        return "线程执行完毕";
    }
}
public class mythread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task=new FutureTask<String>(new mythread01());
        new Thread(task).start();
        System.out.println("线程数据返回"+task.get());
    }
}

在这里插入图片描述
线程的运行状态

在这里插入图片描述
1.任何一个线程的对象都应该使用thread类进行封装,所以线程的启动时使用start()方法,启动的时候,若干个线程都将进入到一个就绪的状态,并没有开始运行,
2.当进入就绪状态后,开始资源的调度,调度成功后进入运行状态,(运行run()方法)线程不可能一直运行下去,中间需要产生一些其他状态,像进入到阻塞状态,
3.当run()方法运行完成,线程的主要任务也就完成了,此时也就可以进入停止状态了,

线程的常用操作方法

多线程的主要操作方法主要在thread类中

线程的命名与取得

在这里插入图片描述
观察线程的命名操作

在这里插入图片描述

package demo;



class  mythread01 implements Runnable { //线程主体类

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class mythread {
    public static void main(String[] args){
       mythread01 mt=new mythread01();
       new Thread(mt,"线程A").start();
       new Thread(mt).start();
       new Thread(mt,"线程B").start();
    }
}

观察一个程序

mt,run();

当直接调用run方法时,运行得出一个main,可知main方法也是一个线程。每当一个Java程序运行时,就会启动一个jvm进程,一个电脑的jvm都会有自己的线程。
主线程可以创建若干子线程,创建子线程完成一些逻辑复杂的运算等。
主线程处理整体流程,子线程处理耗时的流程,

线程的休眠

在这里插入图片描述

观察线程休眠对程序运行速度的影响。

package demo;

public class mythread {
    public static void main(String[] args) {
        new Thread(() -> {
            for (int x = 0; x < 10; x++) {

                System.out.println(Thread.currentThread().getName() + ",x=" + x);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程对象").start();
    }
}
package demo;

public class mythread {
    public static void main(String[] args) throws Exception {
        Runnable run =()->{

            for (int x = 0; x < 10; x++) {
                System.out.println(Thread.currentThread().getName() + ",x=" + x);

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        for (int num=0;num<5;num++){
            new Thread(run,"线程对象-"+num).start();
        }
    }
}

此时产生了五个对象,并且这五个线程兑现的方法体是一样的,此时从程序执行的感觉上来讲,仿佛时若干个线程一起进行了休眠,而后进行了自动唤醒,实际上还是有差别的
在这里插入图片描述
线程中断
线程的中断是由另一个线程完成的,

在这里插入图片描述
范例:观察线程的中断处理操作

package demo;

public class mythread {
    public static void main(String[] args) throws Exception {
    Thread thread=new Thread( ()->{
        System.out.println("疯狂了七十二小时,我要睡觉了!");
        try {
            Thread.sleep(10000);
            System.out.println("睡够了,要出去浪了!!!");
        } catch (InterruptedException e) {
            System.out.println("敢打扰老子,宰了你!!!");
        }

    });

    thread.start();
    thread.sleep(1000);
    if (!thread.isInterrupted()){   //判断是否中断,中断返回true,,没有返回false
        thread.interrupt(); //中断执行
    }
    }
}

线程强制执行
指的是某些线程,满足某些条件后,某个线程将一直独占资源直到线程结束。```



对比下面两个代码

package demo;

public class mythread {
    public static void main(String[] args) throws Exception {
   Thread thread=new Thread(()->{
      for (int x=0;x<100;x++){
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println(Thread.currentThread().getName()+"执行,x="+x);
      }
   }," 玩耍的线程");

   thread.start();
   for (int x=0;x<100;x++){
       thread.sleep(1000);
       System.out.println("霸道的线程,x="+x);
   }
    }
}

在这里插入图片描述

package demo;

public class mythread {
    public static void main(String[] args) throws Exception {
        Thread main=Thread.currentThread();
   Thread thread=new Thread(()->{
      for (int x=0;x<100;x++){

          if (x==3){
              try {
               main.join();
              }catch (InterruptedException e){
                  e.printStackTrace();
              }
          }
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println(Thread.currentThread().getName()+"执行,x="+x);
      }
   }," 玩耍的线程");

   thread.start();
   for (int x=0;x<100;x++){
       thread.sleep(1000);
       System.out.println("霸道的线程,x="+x);
   }
    }
}

在这里使用了线程的强制执行方法,使他强制运行,其他线程等待。
在进行线程强制执行点时候一定要获取强制执行线程对象后才能使用join()方法强制执行。

线程的礼让,

在这里插入图片描述
观察礼让操作

package demo;

public class mythread {
    public static void main(String[] args) throws Exception {

   Thread thread=new Thread(()->{
      for (int x=0;x<100;x++){
          if (x%3==0){
              Thread.yield();
              System.out.println("玩耍的线程礼让执行");
          }
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println(Thread.currentThread().getName()+"执行,x="+x);
      }
   }," 玩耍的线程");

   thread.start();
   for (int x=0;x<100;x++){
       thread.sleep(1000);
       System.out.println("霸道的线程,x="+x);
   }
    }
}

线程的礼让yield();,每次获取资源后只能礼让一次。

线程的优先级

理论上优先级越高的方法,越有可能执行,thread类中针对优先级,有这样两个处理方法:
设置优先级:public final void setpriority(int priority);
获取优先级:public final int get priority();

在进行优先级定义的时候都是通过int型的数字来完成的对于此thread类中定义了三种优先级,
最高优先级:public static final int max_priority,10
中等优先级:public static final int norm_priority,5
最低优先级:public static final int min_priority ,1
范例:观察优先级

package demo;

public class mythread {
    public static void main(String[] args) throws Exception {
           Runnable run=()->{
             for (int x=0;x<10;x++);
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }

               System.out.println(Thread.currentThread().getName()+"执行");
           };
           Thread threadA=new Thread(run,"线程A");
        Thread threadB=new Thread(run,"线程B");
        Thread threadC=new Thread(run,"线程C");


        threadA.setPriority(Thread.MAX_PRIORITY);
        threadB.setPriority(Thread.NORM_PRIORITY);
        threadC.setPriority(Thread.MIN_PRIORITY);
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

主线程的优先级是5,属于中等优先级,
优先级高的只是更可能执行。

线程的同步和死锁

在多线程的处理中,可以利用runnable描述多个线程操作的资源,
而thread描述每一个线程对象,于是当多线程访问同一资源的时候如果处理不当就会产生数据的错误操作。

同步问题的引出,

编写一个卖票的程序

package demo;


class mtthread implements Runnable{
    private  int ticket=10;
    @Override
    public void run() {
        while (true){
            if (this.ticket>0){
                System.out.println(Thread.currentThread().getName()+"买票,ticket"+this.ticket--);
            }else {
                System.out.println("票卖光了");
                break;
            }
        }
    }
}
public class mythread {
    public static void main(String[] args) throws Exception {
        mtthread mt=new mtthread();
        new Thread(mt,"票贩子A").start();
        new Thread(mt,"票贩子B").start();
        new Thread(mt,"票贩子C").start();


    }
}
package demo;


class mtthread implements Runnable{
    private  int ticket=10;
    @Override
    public void run() {
        while (true){
            if (this.ticket>0){
                try {
                    Thread.sleep(100);  //模拟网络延迟
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"买票,ticket"+this.ticket--);
            }else {
                System.out.println(Thread.currentThread().getName()+"卖票,票卖光了");
                break;
            }
        }
    }
}
public class mythread {
    public static void main(String[] args) throws Exception {
        mtthread mt=new mtthread();
        new Thread(mt,"票贩子A").start();
        new Thread(mt,"票贩子B").start();
        new Thread(mt,"票贩子C").start();


    }
}

模拟网路延迟后,程序出现了混乱,出现该混乱的原因

在这里插入图片描述
线程的同步

解决同步问题的关键是锁,

如果想在程序中实现这把锁的功能,可以使用synchronized 关键字来实现,
此关键字可以定义我们的同步方法,或同步代码快,在同步代码块的操作里面的代码只允许一个线程执行,
1.利用同步代码快进行处理

synchronized (同步对象){
                    同步代码快 ;
                }

一般进行同步对象处理的时候可以采用当前对象this进行同步,
范例使用同步代码快解决数据同步访问

package demo;


class mtthread implements Runnable{
    private  int ticket=10;
    @Override
    public void run() {
        while (true){
            synchronized (this){
            if (this.ticket>0){
                try {
                    Thread.sleep(100);  //模拟网络延迟
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"买票,ticket"+this.ticket--);
            }else {
                System.out.println(Thread.currentThread().getName()+"卖票,票卖光了");
                break;
            }}
        }
    }
}
public class mythread {
    public static void main(String[] args) throws Exception {
        mtthread mt=new mtthread();
        new Thread(mt,"票贩子A").start();
        new Thread(mt,"票贩子B").start();
        new Thread(mt,"票贩子C").start();
    }
}

这下多个线程同步访问的问题就解决了,

但是加入同步处理后,程序的整体性能下降了,同步会造成性能的降低,异步才能使性能减低,

利用同步方法解决,子需要在方法定义上加上synchronized即可,

package demo;
class mtthread implements Runnable{
    private  int ticket=10;
    public synchronized boolean sale(){
        if (this.ticket>0){
            try {
                Thread.sleep(100);  //模拟网络延迟
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"买票,ticket"+this.ticket--);
            return true;
        }else {
            System.out.println(Thread.currentThread().getName()+"卖票,票卖光了");
            return false;
    }
    }
    @Override
    public void run() {
        while (this.sale()){
           ; }
        }
    }
public class mythread {
    public static void main(String[] args) throws Exception {
        mtthread mt=new mtthread();
        new Thread(mt,"票贩子A").start();
        new Thread(mt,"票贩子B").start();
        new Thread(mt,"票贩子C").start();
    }
}

在日后学习Java类库时,大都使用同步方法的方式,

线程的死锁
是若干线程彼此等待的过程,

生产者和消费者

生产者:负责消息内容的生产
每当消费者生产完成一项完整的信息之后消费者都要从这里取走信息;
如果生产者没有生产完,消费者要等待,反之,消费者没有消费,生产者要等待。

程序的基本实现

可以将生产者和消费者定义为两个独立的线程类对象,但是对于现在生产的数据可以使用如下的组成,
数据一:title=王建,content=宇宙大帅哥
数据二:title=小高,content=猥琐第一人
既然生产者和消费者是两个独立地线程,那么则需要这两个独立的线程之间就需要一个数据保存点,那么可以独立一个叫做message类实现数据到保存,

在这里插入图片描述
观察代码发现问题

package demo;
class producer implements Runnable{
    private message message;
    public producer(message message){
        this.message= message;
    }
    @Override
    public void run() {
        for (int x=0;x<100;x++){
           if (x%2==0){
               this.message.setTitle("王建");
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               this.message.setContent("宇宙大帅哥");
           } else{
               this.message.setTitle("小高");
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               this.message.setContent("猥琐第一人");
        }
    }
}
}
class consumers implements Runnable{
        private message  message;
        public consumers(message message){
            this.message=message;
    }

    @Override
    public void run() {
           for (int x=0;x<100;x++){
               System.out.println(this.message.getTitle()+"***"+this.message.getContent());
           }
    }
}
class message{
    private String title;
    private String content;

    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}
public class mythread {
    public static void main(String[] args) throws Exception {
       message msg=new message();
       new Thread(new producer(msg)).start();           //启动生产者
       new Thread(new consumers(msg)).start();          //启动消费者

    }
}

通过整个代码的执行发现:
数据不能同步
生产一个取走一个,但是有了重复生产和重复取出的问题,

那如何解决同步问题呢

package demo;
class message{
    private String title;
    private String content;
    public synchronized  void set(String title,String content){
        this.title=title;
        this.content=content;
    }
    public synchronized String get(){
             return this.title+"*"+this.content;
    }
}
class producer implements Runnable{
    private message message;
    public producer(message message){
        this.message= message;
    }
    @Override
    public void run() {
        for (int x=0;x<100;x++){
           if (x%2==0){
               this.message.set("王建","宇宙大帅哥");
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           } else{
               this.message.set("小高","猥琐第一人");
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
        }
    }
}
}
class consumers implements Runnable{
        private message  message;
        public consumers(message message){
            this.message=message;
    }
    @Override
    public void run() {
           for (int x=0;x<100;x++){
               System.out.println(this.message.get());
           }
    }
}
public class mythread {
    public static void main(String[] args) throws Exception {
       message msg=new message();
       new Thread(new producer(msg)).start();           //启动生产者
       new Thread(new consumers(msg)).start();          //启动消费者
    }
}

简单的把message类添加同步锁是不能实现的,

最好的解决方案就是使用等待与唤醒机制,
主要依靠的是object类中提供的方法处理的
等待机制:
死等:public final void wait() throws interrupttedException;
设置等待时间:public final void wait(long timeout) throws interruptttedException;
设置等待时间:public final void wait(long timeout int nanos) throws interruptttedException;
唤醒第一个等待线程:public final void notify();
唤醒所有等待线程:public final void notify();//所有线程中哪个线程优先级高可能先运行,

最后的方案,修改message类

package demo;
class message{

    private String title;
    private String content;
    private boolean flag;    //标志位
    /**
     * flag=true允许生产,不许消费
     * flag=false不允许生产,可以消费
     * */
    public synchronized  void set(String title,String content){
        if (!this.flag){
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.title=title;
        this.content=content;

        this.flag=false;  //生产完成,可以消费
        super.notifyAll();
    }
    public synchronized String get(){

        if (!this.flag){           //还未生产,需要等待;
            try {
                super.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try{
            return this.title+"*"+this.content;
        }finally {
            this.flag=true;
            super.notifyAll();
        }


    }}


class producer implements Runnable{
    private message message;
    public producer(message message){
        this.message= message;
    }
    @Override
    public void run() {
        for (int x=0;x<100;x++){
           if (x%2==0){
               this.message.set("王建","宇宙大帅哥");
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }

           } else{
               this.message.set("小高","猥琐第一人");
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
        }
    }
}
}
class consumers implements Runnable{
        private message  message;
        public consumers(message message){
            this.message=message;
    }
    @Override
    public void run() {
           for (int x=0;x<100;x++){
               System.out.println(this.message.get());
           }
    }
}
public class mythread {
    public static void main(String[] args) {
       message msg=new message();
       new Thread(new producer(msg)).start();           //启动生产者
       new Thread(new consumers(msg)).start();          //启动消费者
    }
}

如何优雅的停止线程

线程启动使用 new thread().start()方法
线程停止使用new thread().stop()方法 //被废除了,可能导致线程的死锁,
现在需要通过柔和的方法进行

观察

package demo;


public class mythread {
    public static boolean flag=true;
    public static void main(String[] args) throws InterruptedException {
    new Thread(()->{
        long num=0;
        while (flag){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"正在运行num:"+num++);
        }
        },"执行线程").start();
    Thread.sleep(200);
    flag=false;
    }
}

这种情况下如果其他线程控制flag了,可能线程不会停止,可能会导致线程死锁,

守护线程

多线程中,主线程或者其他线程还在允许的时候,守护线程一直在后台存在,
在thread类中,提供如下的守护线程的操作方法:
设置为守护线程:public final void setDaemon(Boolean on)
判断是否为守护线程:public final Boolean isDaemon();

代码示例

package demo;


public class mythread {

    public static void main(String[] args) throws InterruptedException {
   Thread usethread= new Thread(()->{
       for (int x=0;x<10;x++){
           try {
               Thread.sleep(50);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName()+"正在运行num:"+x);
        }
        },"用户线程");
        Thread daemonthread= new Thread(()->{

            for (int x=0;x<Integer.MAX_VALUE;x++){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在运行num:"+x);
            }
        },"守护线程");
    daemonthread.setDaemon(true);
    usethread.start();
    daemonthread.start();
    }
}

volatile关键字
主要是在属性定义上使用的,表示此属性为直接数据操作,不对副本操作,
只是节约了操作副本的时间,并不能实现同步

在这里插入图片描述

多线程案例分析

设计四个线程,两个实现加操作,两个实现减操作;

package demo;

class resouce {
    private int num=0;
    private boolean flag=true;
    /***
     *  flag=true 表示可以加法操作,但不能减法操作,反之相反
     */
    public synchronized void add() throws Exception{
        if(this.flag==false){
            super.wait();
        }

        Thread.sleep(200);
        this.num++;
        System.out.println("加法操作"+Thread.currentThread().getName()+"num:"+this.num);
        this.flag=true;
        super.notifyAll();
    }
    public synchronized void sub() throws Exception{
        if(this.flag==true){
            super.wait();
        }
        Thread.sleep(200);
        this.num--;
        System.out.println("减法操作"+Thread.currentThread().getName()+"num:"+this.num);
        this.flag=false;
        super.notifyAll();
    }
}


class addthread implements Runnable{
    private resouce resouce;
    public addthread(resouce resouce){
        this.resouce=resouce;
    }
    @Override
    public void run() {
        for (int x=0;x<50;x++){
            try {
                this.resouce.add();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
class subthread implements Runnable{
    private resouce resouce;
    public subthread(resouce resouce){
        this.resouce=resouce;
    }
    @Override
    public void run() {
        for (int x=0;x<50;x++){
            try {
                this.resouce.sub();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
public class mythread {

    public static void main(String[] args) throws InterruptedException {
         resouce res=new resouce();
         subthread sub=new subthread(res);
         addthread add=new addthread(res);
         new Thread(add,"加法线程-A").start();
        new Thread(add,"加法线程-B").start();
        new Thread(sub,"减法线程-Y").start();
        new Thread(sub,"减法线程-X").start();
}}

生产电脑的案例

在这里插入图片描述
案例简单自己动手you,

在这里插入图片描述

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

在这里插入图片描述
多线程到这里就结束了,

coding

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值