chapter13 多线程

程序、进程、线程区别

程序:是一段代码,静态的

进程:是程序的一次执行过程;正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。(进程是动态的)是一个动的过程,进程的生命周期:有它自身的产生,存在和消亡的过程

线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

并行和并发

并行:多个CPU同时执行多个任务

并发:一个CPU”同时“执行多个任务(采用时间片切换)

创建线程的三种方式

第一种:继承Thread类

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

/*
线程类叫:CreateThread,不是说你名字中带线程单词你就具备多线程能力了(争抢资源能力)
* 现在想要具备能力,要继承一个类:Thread,这样才具备了争抢资源的能力
* */
public class CreateThread extends  Thread {
    /*
      一会线程对象要争抢资源,这个线程要执行的任务是什么?这个任务要放在方法中,
    * 并且这个方法不能是随便写的一个方法,必须是重写Thread类中的run方法
    * 然后线程的任务/逻辑写在run方法中
    * */
    @Override
    public void run() {
        //输出1-10
        for (int i = 1; i < 11; i++) {
            System.out.println(this.getName()+i);
        }
    }
    //构造器
    public CreateThread(String name) {
        super(name);//调用父类的有参构造器
    }
}

public class Test {
    public static void main(String[] args) {
        //给主线程设置名字
        //获取当前线程的方法:Thread.currentThread()
        Thread.currentThread().setName("主线程");
        //主线程中也要输出10个数
        for (int i = 1; i <=10 ; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
        //制造其他线程,跟主线程争抢资源
        //具体的线程对象:子线程
        CreateThread ct=new CreateThread("子线程");//2构造器
        //ct.setName("子线程");1.set方法
        //ct.run();//调用run方法,执行线程中的任务-------->这个run方法不能直接调用,直接调用就会被当作一个普通方法
        //想要子线程真正起作用必须要启动线程
        ct.start();//start方法是Thread类中的方法
        //主线程中也要输出10个数
        for (int i = 1; i <=10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"2"+i);
        }
    }
}

运行结果:
在这里插入图片描述

设置和读取线程名字

1、通过getName,setName方法来进行设置读取,代码见上

2、通过构造器设置名字,代码见上

习题:买火车票

原理:每个窗口都是一个具体的线程对象
在这里插入图片描述

public class BuyTicketThread extends Thread{
    //一共10张票
    static int ticketNum=10;//这10张票要共享,无论有几张票,无论有几个线程对象,这10张票都要共享,不能多出来
    //static的作用就保证了共享
    //每个窗口都是一个线程对象:每个对象执行的代码要放入run方法中
    //必须重写一个run方法
    @Override
    public void run() {
        //每个窗口后有100个人在抢票
        for (int i = 1; i <= 100; i++) {
            if (ticketNum > 0) {//票数大于0,我们才抢票
                System.out.println("我在"+this.getName()+"买到了第" + ticketNum-- + "张火车票");
            }
        }
    }

    //加带参构造器设置线程名字
    public BuyTicketThread(String name) {
        super(name);
    }
}
public class TestBuyTicket {
    public static void main(String[] args) {
        //多个窗口抢票,三个窗口三个线程对象
        BuyTicketThread t1=new BuyTicketThread("窗口1:");
        t1.start();
        BuyTicketThread t2=new BuyTicketThread("窗口2:");
        t2.start();
        BuyTicketThread t3=new BuyTicketThread("窗口3:");
        t3.start();
    }
}

结果:出现了0有问题,后续会说明

在这里插入图片描述

public class BuyTicket implements Runnable{
    int ticketNum=10;
    @Override
    public void run() {
        for (int i = 1; i <=100 ; i++) {
            if (ticketNum > 0) {//票数大于0,我们才抢票
                System.out.println("我在"+Thread.currentThread().getName()+"买到了第" + ticketNum-- + "张火车票");
            }
        }
    }
}
public class TestTicket {
    public static void main(String[] args) {
        //线程对象共享一个就可以了
        BuyTicket bt=new BuyTicket();
        Thread t1=new Thread(bt,"窗口1");
        t1.start();
        Thread t2=new Thread(bt,"窗口2");
        t2.start();
        Thread t3=new Thread(bt,"窗口3");
        t3.start();
    }
}

在这里插入图片描述

第二种:实现Runnable接口

/*
* TestRunnable实现了这Runnable接口,才会变成一个线程类
* */
public class TestRunnable implements Runnable{
    //在这里run方法不是重写,而是实现
    //线程中想要执行的任务要放到run方法中
    @Override
    public void run() {
        //输出1-10
        for (int i = 1; i <=10 ; i++) {
            //获取当前线程名字的方法
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //创建子线程对象
        TestRunnable tr=new TestRunnable();
        //tr没有start方法,必须借助Thread类完成
        Thread t=new Thread(tr,"子线程");
        t.start();
        
        //主线程:打印1-10
        for (int i = 1; i <=10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}

运行结果:
在这里插入图片描述

第二种方式买火车票,见火车票上
在这里插入图片描述

第三种:实现Callable接口

上述两种方式不足的地方:

1、run方法不能有返回值

2、不能抛出异常
在这里插入图片描述

public class TestCallable implements Callable<Integer> {
    /*
    * 1.实现callable接口可以不带泛型,如果不带泛型,那么call方法返回值就是object类型
    * 2.如果带泛型,那么call方法的返回值就是泛型对应的类型
    * 3.从call方法我们看到,这个方法有返回值,还可以抛出异常
    * */
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(10);//返回10以内的随机数
    }
}
public class TestC {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //定义一个线程对象
        TestCallable tc=new TestCallable();
        FutureTask ft=new FutureTask(tc);//runnable接口的实现类
        Thread t=new Thread(ft);
        t.start();
        //获取线程得到的返回值,用ft,get抛异常
        Object o = ft.get();
        System.out.println(o);
    }
}

如果想有返回值的话,考虑此种方法

线程的生命周期

1、线程开始----》线程消亡

2、线程经历了哪些阶段;重点
不完全图:
在这里插入图片描述
完整图:
在这里插入图片描述

线程的常见方法

在这里插入图片描述

设置优先级

在这里插入图片描述

public class TestThread extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            System.out.println(i);
        }
    }
}
class TestThread01 extends Thread{
    @Override
    public void run() {
        for (int i = 20; i <=30 ; i++) {
            System.out.println(i);
        }
    }
}
class test01{
    public static void main(String[] args) {
        TestThread tt1=new TestThread();
        tt1.setPriority(2);//优先级别低
        //System.out.println("priority:"+tt1.getPriority());
        tt1.start();
        TestThread01 tt2=new TestThread01();
        tt2.setPriority(9);//优先级别高
        tt2.start();
    }
}

join方法

在这里插入图片描述

public class TestJoin extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            System.out.println(this.getName()+ "--------"+i);
        }
    }

    public TestJoin(String name) {
        super(name);
    }
}
class Test02{
    public static void main(String[] args) throws InterruptedException {
        for (int i = 1; i <=20; i++) {
            if(i==6){
                TestJoin tj=new TestJoin("子线程");
                tj.start();
                tj.join();//半路杀出个程咬金
            }
            System.out.println(Thread.currentThread().getName()+ "--------"+i);
        }
    }
}

运行结果:
在这里插入图片描述

sleep方法

人为的制造阻塞事件

public class TestSleep {
    public static void main(String[] args) {
        long startTime=System.currentTimeMillis();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime=System.currentTimeMillis();
        System.out.println("等待时间:"+(endTime-startTime));//3014
        System.out.println("0000000");
    }
}

案例:完成秒表的功能

public class TestClock {
    public static void main(String[] args) {
        //2.定义时间格式
        DateFormat df=new SimpleDateFormat("HH:mm:ss");
        while(true){
            //1.获取当前时间
            Date d=new Date();
            //3.按上面定义的格式将date类型转为指定格式的字符串
            System.out.println(df.format(d));
            //4.等待1秒后在输出
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

setDaemon方法

设置伴随线程
在这里插入图片描述

public class SetThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <=1000 ; i++) {
            System.out.println("子线程----"+i);
        }
    }
}
class set01{
    public static void main(String[] args) {
        //创建并启动线程
        //将子线程设置为主线程的伴随线程,主线程停止的时候,子线程也停止
        SetThread1 st=new SetThread1();
        st.setDaemon(true);//设置伴随线程,先设置再启动
        st.start();
        //主线程中输出1-10
        for (int i = 1; i <=10 ; i++) {
            System.out.println("main----"+i);
        }
    }
}

stop方法

public class set01{
    public static void main(String[] args) {
        for (int i = 1; i <=100 ; i++) {
            if(i==6){
                Thread.currentThread().stop();//过期方法,不建议使用
            }
            System.out.println("main----"+i);
        }
    }
}

线程安全问题(⭐)

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

上面代码出现了问题:出现了重票,错票 -------》线程安全问题

原因:多个线程在争抢资源的过程中,导致共享的资源出现问题;一个线程还没执行玩,另一个线程就参与进来了,开始争抢。

解决:在程序中加个锁----》加同步----》加同步监视器

方法1:同步代码块

public class BuyTicket implements Runnable{
    int ticketNum=10;
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            synchronized (this) {//加锁的位置要自己决定;
                //把具有安全隐患的代码锁住即可,如果锁多了效率就会低----》this就是这个锁
                if (ticketNum > 0) {
                    System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车票");
                }
            }
        }
    }
}
public class TestTicket {
    public static void main(String[] args) {
        BuyTicket bt=new BuyTicket();//实际上只有一个对象
        Thread t1=new Thread(bt,"窗口1");
        t1.start();
        Thread t2=new Thread(bt,"窗口2");
        t2.start();
        Thread t3=new Thread(bt,"窗口3");
        t3.start();
    }
}

在继承thread类下的买火车票加锁了之后还有问题
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class BuyTicketThread extends Thread{
    final String a="abc";//字符串在内存中独一份
    static Object o=new Object();//不加static的话仍然是多把锁
    static int ticketNum=10;
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            //锁必须是多个线程用的是同一把锁!!!(上厕所红绿杯子案例)
            //synchronized ("abc")  1.引用数据类型锁
            //synchronized (o)也行 ;2.没有任何业务含义的锁
            //synchronized (BuyTicketThread.class) 3.共享的资源
            //synchronized (a){a="def";} 4.可更改就不行,定义为final可以
            //6.final只是不能更改地址,如果final修饰对象,对象的属性依然可以改
            synchronized (BuyTicketThread.class) {//this指的是当前调用run方法的那个对象
                if (ticketNum > 0) {
                    System.out.println("我在" + this.getName() + "买到了第" + ticketNum-- + "张火车票");
                }
            }
        }
    }
    public BuyTicketThread(String name) {
        super(name);
    }
}
public class TestBuyTicket {
    public static void main(String[] args) {
        //多个窗口抢票,三个窗口三个线程对象
        //这有三个线程对象,锁 this 没有用
        BuyTicketThread t1=new BuyTicketThread("窗口1:");
        t1.start();
        BuyTicketThread t2=new BuyTicketThread("窗口2:");
        t2.start();
        BuyTicketThread t3=new BuyTicketThread("窗口3:");
        t3.start();
    }
}

在这里插入图片描述

cpu的切换—》线程的切换

方法2:同步方法

public class BuyTicket implements Runnable{
    int ticketNum=10;
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            buyTicket();
        }
    }
    public synchronized void buyTicket(){//锁住的是this
        if (ticketNum > 0) {
            System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车票");
        }
    }
}

这个代码对,因为测试时只有一个对象t

但是对于下面代码来说,锁住方法锁住的是t1,t2,t3,不是一把锁没有用(没加static前)

加一个static就可以解决问题

public class BuyTicketThread extends Thread{
    //一共10张票
    static int ticketNum=10;//这10张票要共享,无论有几张票,无论有几个线程对象,这10张票都要共享,不能多出来
    //static的作用就保证了共享
    //每个窗口都是一个线程对象:每个对象执行的代码要放入run方法中
    //必须重写一个run方法
    @Override
    public void run() {
        //每个窗口后有100个人在抢票
        for (int i = 1; i <= 100; i++) {
            //锁必须是多个线程用的是同一把锁!!!
            buyTicket();
        }
    }
    public static synchronized void buyTicket(){//不加static锁住的还是this,要加static
        //加上static锁住的是同步监视器:BuyTicketThread.class
            if (ticketNum > 0) { 
                System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车票");
            }
    }

    //加带参构造器设置线程名字
    public BuyTicketThread(String name) {
        super(name);
    }
}

static代码里不能用this
在这里插入图片描述

方法3:Lock锁

在这里插入图片描述

public class BuyTicket implements Runnable{
    int ticketNum=10;
    //拿来一把锁:lock是一个接口,用实现类来创建,多态,可以使用不同的实现类
    Lock lock=new ReentrantLock();

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            //使用锁、打开锁:
            lock.lock();
            try{
                if (ticketNum > 0) {
                System.out.println("我在" + Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车票");
                }
            }catch(Exception ex){
                ex.printStackTrace();
            }finally {
                //如果内部代码出现问题,这个锁可能就没有得到释放,为了解决这个问题,使用try-catch-finally
                //关闭锁---->即使有异常,这个锁也可以得到释放
                lock.unlock();
            }
        }
    }
}

线程同步的缺点

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

线程通信问题

生产者和消费者

在这里插入图片描述

共享问题:static;线程同步

分解1:

在这里插入图片描述

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

第一个问题:通过线程通信解决

第二个问题:通过加锁解决

public class Product {//商品类
    //属性:品牌和名字
    private String name;
    private String brand;

    //setter getter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }
}
public class Producer extends Thread{//生产者线程
    //共享的商品
    private Product p;

    //确保生产者消费者共享的商品是一个,通过定义构造器
    public Producer(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            if(i%2==0){
                //生产费列罗巧克力
                p.setBrand("费列罗");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p.setName("巧克力");
            }else{
                //生产哈尔滨啤酒
                p.setBrand("哈尔滨");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                p.setName("啤酒");
            }
            //将生产信息做一个打印
            System.out.println("生产者生产了:"+p.getBrand()+"----"+p.getName());
        }
    }
}
public class Consumer extends Thread{//消费者线程
    //共享商品:
    private Product p;

    public Consumer(Product p) {//确保商品从外界传过来
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <=10 ; i++) {
            System.out.println("消费者消费了:"+p.getBrand()+"----"+p.getName());
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //共享的商品
        Product p=new Product();
        //创建生产者和消费者线程
        Producer pt=new Producer(p);
        Consumer ct=new Consumer(p);
        pt.start();
        ct.start();
    }
}

分解2:解决了第二个问题

利用同步代码块解决问题
public class Producer extends Thread{//生产者线程
    //共享的商品
    private Product p;

    //确保生产者消费者共享的商品是一个,通过定义构造器
    public Producer(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            synchronized (p){//这个锁需要是生产者和消费者共享的,而p正是共享的
                if(i%2==0){
                    //生产费列罗巧克力
                    p.setBrand("费列罗");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    p.setName("巧克力");
                }else{
                    //生产哈尔滨啤酒
                    p.setBrand("哈尔滨");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    p.setName("啤酒");
                }
                //将生产信息做一个打印
                System.out.println("生产者生产了:"+p.getBrand()+"----"+p.getName());
            }
        }
    }
}
public class Consumer extends Thread{//消费者线程
    //共享商品:
    private Product p;

    public Consumer(Product p) {//确保商品从外界传过来
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <=10 ; i++) {
            synchronized (p){
                System.out.println("消费者消费了:"+p.getBrand()+"----"+p.getName());
            }
        }
    }
}
利用同步方法解决问题

在商品里面设置同步方法,生产和消费直接调用

public class Product {//商品类
    //属性:品牌和名字
    private String name;
    private String brand;

    //setter getter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    //生产商品
    public synchronized void setProduct(String brand,String name){
        this.setBrand(brand);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        //将生产信息做一个打印
        System.out.println("生产者生产了:"+this.getBrand()+"----"+this.getName());
    }

    //消费商品
    public synchronized void getProduct(){
        System.out.println("消费者消费了:"+this.getBrand()+"----"+this.getName());
    }
}
public class Producer extends Thread{//生产者线程
    //共享的商品
    private Product p;

    //确保生产者消费者共享的商品是一个,通过定义构造器
    public Producer(Product p) {
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <=10; i++) {
            if(i%2==0){
                //生产费列罗巧克力
                p.setProduct("费列罗", "巧克力");
            }else{
                p.setProduct("哈尔滨", "啤酒");
            }
        }
    }
}
public class Consumer extends Thread{//消费者线程
    //共享商品:
    private Product p;

    public Consumer(Product p) {//确保商品从外界传过来
        this.p = p;
    }

    @Override
    public void run() {
        for (int i = 1; i <=10 ; i++) {
            p.getProduct();
        }
    }
}

分解3:

原理:
在这里插入图片描述

public class Product {//商品类
    //属性:品牌和名字
    private String name;
    private String brand;

    //引入一个灯,ture 红色 false 绿色
    boolean flag=false;//默认情况下没有商品,生产者需要先生产 消费者再消费

    //setter getter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    //生产商品
    public synchronized void setProduct(String brand,String name){
        if(flag==true){//灯是红色,有商品,生产者不生产,等着消费者消费
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //灯是绿色,就生产
        this.setBrand(brand);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        //将生产信息做一个打印
        System.out.println("生产者生产了:"+this.getBrand()+"----"+this.getName());
        //生产完之后,灯变红色
        flag=true;
        //告诉消费者赶紧来消费
        notify();
    }

    //消费商品
    public synchronized void getProduct(){
        if(!flag){//flag==false没有商品,等待生产者生产
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有商品,消费
        System.out.println("消费者消费了:"+this.getBrand()+"----"+this.getName());

        //消费完,变绿色
        flag=false;
        //通知生产者
        notify();
    }
}

consumer和producer,test里面不变和分解2一样

※线程通信原理

在这里插入图片描述

沿着wait之后的方法,所以不把代码放入else内

注意:wait 和 notify 是必须放在同步方法或代码块里才生效的(因为是在同步的基础上进行线程的通信才是有效的)

注意:sleep 和 wait 的区别:sleep 进入阻塞状态,但没有释放锁;wait进入阻塞状态,但同时释放了锁

Lock锁情况下的线程通信

多个生产者,多个消费者

把等待队列分开,把生产者和消费者放入不同的等待池中

一个锁池,多个等待池
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class Product {//商品类
    //属性:品牌和名字
    private String name;
    private String brand;

    //声明一个Lock锁
    Lock lock=new ReentrantLock();
    //生产者等待队列:
    Condition pc=lock.newCondition();
    //消费者等待队列:
    Condition cc=lock.newCondition();

    //引入一个灯,ture 红色 false 绿色
    boolean flag=false;//默认情况下没有商品,生产者需要先生产 消费者再消费

    //setter getter方法
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    //生产商品
    public void setProduct(String brand,String name){
        lock.lock();
        try{
            if(flag==true){//灯是红色,有商品,生产者不生产,等着消费者消费
                try {
                    //wait();必须结合synchronized
                    //生产者阻塞,生产者进入等待队列
                    pc.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //灯是绿色,就生产
            this.setBrand(brand);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setName(name);
            //将生产信息做一个打印
            System.out.println("生产者生产了:"+this.getBrand()+"----"+this.getName());
            //生产完之后,灯变红色
            flag=true;
            //告诉消费者赶紧来消费
            //notify();
            cc.signal();
        }finally {
            lock.unlock();//为了防止开不了锁,使用try-finally
        }
    }

    //消费商品
    public void getProduct(){
        lock.lock();
        try{
            if(!flag){//flag==false没有商品,等待生产者生产
                try {
                    //wait();消费者等待,消费者线程进入等待队列
                    cc.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //有商品,消费
            System.out.println("消费者消费了:"+this.getBrand()+"----"+this.getName());

            //消费完,变绿色
            flag=false;
            //通知生产者
            //notify();
            pc.signal();
        }finally {
            lock.unlock();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值