javaSE每天练程序——day17线程和进程,线程的三种创建方式,购票,加锁,死锁

目录

  • 多线程
  • 进程的概述和多进程的意义
  • 线程的概述和多线程的意义
  • JVM运行原理以及JVM启动的线程探讨
  • 实现多线程
  • 线程调度
  • 线程控制
    多线程(进程概述及多进程的意义)(理解)

多线程(进程概述及多进程的意义)(理解)

  • 线程依赖进程
进程

位置:通过任务管理器我们可以看到进程的存在
概念:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。

  • 多进程的意义:
    单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程)所以我们常见的操作系统都是多进程操作系统。比如:Windows,Mac和Linux等,能在同一个时间段内执行多个任务。
    对于单核计算机来讲,游戏进程和音乐进程是同时运行的吗?不是。
    cpu在某个节点只能做一件事情,cpu可以在游戏和音乐进程中高速切换,提高了cpu的利用率

多线程(线程概述及多线程的意义及并行和并发的区别)(理解)

  • A:什么是线程
    在进程内部可执行很多任务,每个任务叫线程.
    进程是拥有资源的基本单位,线城市Cpu调度的基本单位
  • B:多线程有什么意义呢?
    多线程不是为了提高执行速度,而是提高应用程序的使用率。
    进程包含线程,程序在运行的过程中都是互相抢占cpu的,程序在多线程比在单线程抢占到的概率大,提高了程序和cpu的使用率,在抢占过程中只是抢占的概率比较大,至于谁能抢占到不确定,因为线程具有随机性。
    在这里插入图片描述

线程(Java程序运行原理和JVM的启动是多线程的吗)(理解)

是,java命名启动java虚拟机=于启动了一个应用程序=启动了一个进程,该进程就会去调用一个主线程main方法在主线程中运行。
jvm至少启动了一个主线程+垃圾回收线程

并发和并行

  • 什么是并发 ?
    在几个任务之间快速切换,达到同时执行的效果
  • 什么是并行 ?
    同时执行不同的线程

总结

线程就是独立的执行路径;
在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
main() 称之为主线程,为系统的入口,用于执行整个程序;
在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
线程会带来额外的开销,如cpu调度时间,并发控制开销。
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程创建

在这里插入图片描述

thread类

  • 步骤:
    1.自定义线程类继承Thread类
    2.重写run()方法,编写线程执行体
    3.创建线程对象,调用start()方法启动线程

  • Thread类的基本获取和设置方法
    public final String getName()//获取线程名称
    public final void setName(String name)//设置线程名称
    其实通过构造方法也可以给线程起名字
    思考:
    如何获取main方法所在的线程名称呢?
    public static Thread currentThread()//获取当前执行的线程
    /**

  • 小问题:
    启动线程:start方法,同一个线程不可以多次启动
    为什么重写run()方法?Thread类中被被线程执行的耗时操作
    案例1:设置线程的名字

package day20190728.study01.线程的创建方式一;

/**
 * @description: 创建的thread类
 * @author: @李小白
 * @create: 2019-07-28 10:31
 */
public class Threads extends Thread{
    @Override
    public void run() {
//        String name = this.getName();//命名方式一
        String name = Thread.currentThread().getName();//命名方式二 ,我选,可以给主方法命名
        //给他一个循环次数
        for (int i = 0; i < 100; i++) {
            System.out.println(name+"-------"+i);
        }
    }
}

package day20190728.study01.线程的创建方式一;

/**
 * @description: 改名字的两种方式
 * @author: @李小白
 * @create: 2019-07-28 10:31
 */
public class Demo给线程改名字 {
    public static void main(String[] args) {
        //给主线程设值名字
        Thread thread = Thread.currentThread();
        thread.setName("主线程1");
        System.out.println(thread.getName());

       //线程1
        Threads threads = new Threads();
        //给线程1设置名字
        threads.setName("线程1");
        threads.start();
        //执行主程序
        System.out.println("主线程执行完了");
        System.out.println("主线程执行完了");
        System.out.println("主线程执行完了");
        System.out.println("主线程执行完了");
        System.out.println("主线程执行完了");
    }
}

案例2:设置优先级

  • 线程有两种调度模型:
    分时调度模型:时间平均分配
    抢占式调度模型:优先级高的优先抢占线程,优先级相同就随机选择一个,优先极高的获取率高一些
    java是使用抢占调度模型。
  • 如何设置和获取线程优先级
    public final int getPriority() //获取线程的优先级
    public final void setPriority(int newPriority)//设置线程的优先级
  • 小问题:
    有的时候我们给线程设置了指定的优先级,但是该线程并不是按照优先级高的线程执行,那是为什么呢?
    优先级高并不代表一定会执行,只是被cpu执行的概率及较大,因为线程具有随机性。
Thread仍然不变
package day20190728.study01.线程的创建方式一;

/**
 * @description: 给线程设值优先级
 * @author: @李小白
 * @create: 2019-07-28 10:56
 */
public class Demo设值优先级 {
    public static void main(String[] args) {
        Threads threads1 = new Threads();
        Threads threads2 = new Threads();

        threads1.start();
        threads2.start();

        threads1.setName("线程1");
        threads2.setName("线程2");

        hreads1.setPriority(8);//设值优先级1-10

        threads2.setPriority(2);//设值优先级1-10

//        System.out.println(threads1.getPriority());//输出优先级
//        System.out.println(threads2.getPriority());//输出优先级
    }
}

案例三:线程休眠

  • 线程休眠: public static void sleep(long millis) 线程休眠
package day20190728.study01.线程的创建方式一;

/**
 * @description: 创建的thread类
 * @author: @李小白
 * @create: 2019-07-28 10:31
 */
public class Threads extends Thread{
    @Override
    public void run() {
//        String name = this.getName();//命名方式一
        String name = Thread.currentThread().getName();//命名方式二 ,我选,可以给主方法命名
        try {
            Thread.sleep(2000);//让整个主程序休眠2秒,有异常try catch
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //给他一个循环次数
        for (int i = 0; i < 100; i++) {
            System.out.println(name+"-------"+i);
        }
    }
}

package day20190728.study01.线程的创建方式一;

/**
 * @description:
 * @author: @李小白
 * @create: 2019-07-28 11:13
 */
public class Demo线程休眠 {
    public static void main(String[] args) throws InterruptedException {
        Threads threads1 = new Threads();//线程1
        Threads threads2 = new Threads();//线程2
        threads1.sleep(20);//让线程1休眠,在启动前休眠,有异常抛出
        threads1.start();
        threads2.sleep(20);//让线程2休眠,在启动前休眠
        threads2.start();

        threads2.start();
    }
}

案例四:

  • 加入线程: public final void join()
Threak不变
package day20190728.study01.线程的创建方式一;

/**
 * @description: 一个执行完另一个才能执行
 * @author: @李小白
 * @create: 2019-07-28 11:17
 */
public class Demo串行执行 {
    public static void main(String[] args) throws InterruptedException {
        Threads threads1 = new Threads();//线程1
        Threads threads2 = new Threads();//线程2
      
        threads1.start();
        threads1.join();//先执行完线程1,有异常抛出,在开启之后执行
        threads2.start();
        threads2.join();//先执行完线程2,有异常抛出,在开启之后执行
    }
}

线程礼让

  • public static void yield(): 一人执行一次
  • 小问题:
    为什么效果不是很明显?
    礼让的暂停时间是短暂的,在这个线程完毕后,其他线程还有抢占到,他就会与其他线程再次抢占cpu的执行权
两个线程的启动不变
package day20190728.study01.线程的创建方式一;

/**
 * @description: 创建的thread类
 * @author: @李小白
 * @create: 2019-07-28 10:31
 */
public class Threads extends Thread{
    @Override
    public void run() {
//        String name = this.getName();//命名方式一
        String name = Thread.currentThread().getName();//命名方式二 ,我选,可以给主方法命名
        try {
            Thread.sleep(2000);//让整个主程序休眠2秒,有异常try catch
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread.yield();
        //给他一个循环次数
        for (int i = 0; i < 100; i++) {
            System.out.println(name+"-------"+i);
        }
    }
}

守护线程

  • public final void stop(): 停止线程的运行
  • public void interrupt(): 中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞
Thread类不变
package day20190728.study01.线程的创建方式一;

/**
 * @description: 守护线程
 * @author: @李小白
 * @create: 2019-07-28 11:27
 */
public class Demo守护线程 {
    public static void main(String[] args) {
        Threads threads1 = new Threads();//线程1
        Threads threads2 = new Threads();//线程2
        threads1.setDaemon(true);//守护线程1,在启动前进行守护
        threads1.start();
        threads2.setDaemon(true);//守护线程2,在启动前进行守护
        threads2.start();
    }
}

强行停止线程,打断线程阻塞

Thread不变
package day20190728.study01.线程的创建方式一;

/**
 * @description: 守护线程
 * @author: @李小白
 * @create: 2019-07-28 11:27
 */
public class Demo守护线程 {
    public static void main(String[] args) throws InterruptedException {
        Threads threads1 = new Threads();//线程1

        threads1.start();
        Thread.sleep(200);
        threads1.stop();
        threads1.interrupt();//打断线程的一个阻塞状态

    }
}

用线程的启动方式一,进行复制文件

public class MyTest {
    public static void main(String[] args) throws IOException {
        //一个线程复制一个 文件
        CopyThread th1 = new CopyThread();
        th1.start();
        //一个线程复制一个 文件
        new CopyThread2().start();
    }
}

public class CopyThread extends Thread{
    @Override
    public void run() {
        //我复制第一个文件
        try {
            Files.copy(Paths.get("D:\\HBuilderX.1.3.2.20181214.full.zip"), Paths.get("E:\\HBuilderX.1.3.2.20181214.full.zip"), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


public class CopyThread2 extends Thread{
    @Override
    public void run() {
        //我复制第二文件
        try {
            Files.copy(Paths.get("MyTest.java"), Paths.get("E:\\hehe.txt"), StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

多线程(多线程程序实现的方式2)(掌握)

  • 步骤:
    1.创建一个类,实现 Runable接口,完成run创建
    2.创建一个类,创建接口对象,创建Thread对象
package day20190728.study01.线程的创建方式二;

/**
 * @description: 创建接口
 * @author: @李小白
 * @create: 2019-07-28 11:51
 */
public class Runables implements Runnable {
    @Override
    public void run() {
        
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"----"+i);
        }
    }
}

package day20190728.study01.线程的创建方式二;

/**
 * @description: 创建的方式二
 * @author: @李小白
 * @create: 2019-07-28 11:50
 */
public class Demo创建方式二 {
    public static void main(String[] args) {
        Runables runables = new Runables();//线程==需要解决的一个问题

        Thread thread1 = new Thread();//线程1
        Thread thread2 = new Thread();//线程2

        thread1.start();
        thread2.start();


    }
}

多线程(多线程程序实现的方式3)(掌握)

相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。

  • 步骤:
  1. 创建一个类,实现Callable 接口。 实现call方法
  2. 创建一个类,创建 new FutureTask()将Callable接口的子类对象作为参数传进去
  3. 创建Thread类,将FutureTask对象作为参数传进去
  4. 开启线程
package org.westos.demo3;

import java.util.concurrent.Callable;


public class MyCallable implements Callable<Integer> {
    //call方法就是线程要执行的方法
    @Override
    public Integer call() throws Exception {
        System.out.println("线程执行了");
        int sum=0;
        for (int i = 1; i <= 100; i++) {
            sum+=i;
        }
        return sum;
    }
}


public class MyTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程的方式3
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(myCallable);
        Thread thread = new Thread(task);
        thread.start();
        //线程执行完之后,可以获取结果
        Integer integer = task.get();
        System.out.println(integer);
    }
}

多线程(继承Thread类的方式卖电影票案例)(理解)

方法一:
Thread方法购票

package day20190727.study02.购票;

/**
 * @description: 第一种方式
 * @author: @李小白
 * @create: 2019-07-27 14:41
 */
public class Thread1 extends Thread{
    @Override
    public void run() {
       int piao=100;
       while (true){
           try {
               Thread.sleep(1);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           if(piao>1){
               System.out.println(Thread.currentThread().getName()+"正在售"+(piao--)+"张票");
           }
       }
    }
}
package day20190727.study02.购票;

/**
 * @description: 方式一
 * @author: @李小白
 * @create: 2019-07-27 14:40
 */
public class Demo03购票1 {
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread1 thread2 = new Thread1();
        Thread1 thread3 = new Thread1();
        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

多线程(实现Runnable接口的方式卖电影票)(理解)

方法二:
Runnale购票

package day20190727.study02.购票;

/**
 * @description:
 * @author: @李小白
 * @create: 2019-07-27 14:49
 */
public class Runnable2 implements Runnable {
     static int piao=1000;
    static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {

                if (piao >= 1) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在售" + (piao--) + "张票");
                }

            }
        }
    }
}

package day20190727.study02.购票;

/**
 * @description:
 * @author: @李小白
 * @create: 2019-07-27 14:42
 */
public class Demo04购票2 {
    public static void main(String[] args) {
        Runnable2 runnable2 = new Runnable2();
        Thread thread1 = new Thread(runnable2);
        Thread thread2 = new Thread(runnable2);
        Thread thread3 = new Thread(runnable2);
        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");
        thread1.start();
        thread2.start();
        thread3.start();



    }
}

多线程(买电影票出现了同票和负数票的原因分析)(理解)

A:加入延迟
我们前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,
售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟
改实现接口方式的卖票程序,每次卖票延迟100毫秒

多线程(线程安全问题的产生原因分析)(理解)

A:首先想为什么出现问题?(也是我们判断是否有问题的标准)
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
B:如何解决多线程安全问题呢?
基本思想:让程序没有安全问题的环境。
怎么实现呢?
把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

synchronized(对象){//不能在括号了直接new 对象 new 了 就没效果
要被同步的代码 ;
}

  • 这个同步代码块保证数据的安全性的一个主要因素就是这个对象
    注意这个对象 要定义为静态成员变量 才能被所有线程共享
  • 需要这个对象被所有的线程对象所共享
  • 这个对象其实就是一把锁.
  • 这个对象习惯叫做监视器

多线程(同步代码块的方式解决线程安全问题及解释以及同步的特点及好处和弊端)(掌握)

同步代码块的格式

  • 格式:
    synchronized(对象){
    需要同步的代码;
    }
    同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能
  • D:同步的好处: 同步的出现解决了多线程的安全问题。
  • E:同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

多线程(同步代码块的锁问题以及同步方法的应用和锁问题)(掌握)

A:案例演示: 同步代码块的锁问题
B:同步方法: 就是把同步关键字加到方法上
C:案例演示: 同步方法的锁对象是什么呢?
D:案例演示: 如果是静态方法,同步方法的锁对象又是什么呢?

同步代码块的锁对象: 任意一个对象
同步方法的锁对象: 是this
静态同步方法的锁对象:就是当前类对应的字节码文件对象
方法一:一个方法上锁

public class MyRunnable implements Runnable {
    static int piao = 100;
    static Object obj = new Object();
    int i=1;
    @Override
    public void run() {

            while (true) {
                //我们在实际网上购票时,会有一些网络延迟,我们可以使用休眠来模拟一下
                //剩余最后两张票
                //th1 th2 th3
                if(i%2==0){
                    //同步代码使用任意对象,作为锁
                    synchronized (MyRunnable.class) { //锁 ,其实就是一个任意对象,多个线程要共享一把锁
                        if (piao >= 1) {
                            try {
                                Thread.sleep(10);//模拟网络延迟 th1
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName() + "正在出售" + (piao--) + "张票");
                        }
                    }
                }else{
                    cellPiao2();
                }
                i++;

                //释放锁
            }


    }


    //方法上加有一个synchronized关键字我们叫做同步方法
    //同步方法使用的所对象不是任意对象,他用的锁是this
    private synchronized void cellPiao() {
            if (piao >= 1) {
                try {
                    Thread.sleep(20);//模拟网络延迟 th1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售" + (piao--) + "张票");
            }
    }
    //静态同步方法使用的锁对象,不是任意对象,他用的锁是,当前类的 字节码类型
    private synchronized static void cellPiao2() {
        if (piao >= 1) {
            try {
                Thread.sleep(20);//模拟网络延迟 th1
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售" + (piao--) + "张票");
        }
    }
}

public class MyTest {
    public static void main(String[] args) {
        //A:
        //案例演示
        //需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,
        //请设计一个程序模拟该电影院售票。
        //我们在模拟 了网络延迟后,出现了
            //i--
        // 相同票:是由于原子性所导致的 piao--  他不是一个原子性的操作 原子性:不可再分割
        // 0票或者负数票:由于线程的随机性所导致
        //在多线程的环境下出现了一些线程安全方面的问题
        //出现数据安全问题,要满足以下三个条件
        //1.要是多线程环境
        //2.多个线程在并发操作共享数据
        //3.有多条语句在操作这个共享数据

        //我们现在可以使用同步代码块来解决线程安全问题
       // synchronized (锁对象){
        //   放置有可能出现线程安全问题代码
        // }
        MyRunnable myRunnable1 = new MyRunnable();
        Thread th1 = new Thread(myRunnable1);
        Thread th2 = new Thread(myRunnable1);
        Thread th3 = new Thread(myRunnable1);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
        //同步代码块,用的锁对象,是任意锁对象
        //同步方法用的锁对象是this
        //静态同步方法用的锁对象是 字节码类型
    }
}

方式二:两个方法都上锁

public class MyRunnable implements Runnable {
    static int piao = 100;
    static Object obj = new Object();
    int i=1;
    @Override
    public void run() {
            while (true) {
                 //cellPiao();
                cellPiao2();
            }
    }


    //方法上加有一个synchronized关键字我们叫做同步方法
    //同步方法使用的所对象不是任意对象,他用的锁是this
    private synchronized void cellPiao() {
            if (piao >= 1) {
                try {
                    Thread.sleep(20);//模拟网络延迟 th1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售" + (piao--) + "张票");
            }
    }
    //静态同步方法使用的锁对象,不是任意对象,他用的锁是,当前类的 字节码类型
    private synchronized static void cellPiao2() {
        if (piao >= 1) {
            try {
                Thread.sleep(20);//模拟网络延迟 th1
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售" + (piao--) + "张票");
        }
    }
}

//A:
        //案例演示
        //需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,
        //请设计一个程序模拟该电影院售票。
        //我们在模拟 了网络延迟后,出现了
            //i--
        // 相同票:是由于原子性所导致的 piao--  他不是一个原子性的操作 原子性:不可再分割
        // 0票或者负数票:由于线程的随机性所导致
        //在多线程的环境下出现了一些线程安全方面的问题
        //出现数据安全问题,要满足以下三个条件
        //1.要是多线程环境
        //2.多个线程在并发操作共享数据
        //3.有多条语句在操作这个共享数据

        //我们现在可以使用同步代码块来解决线程安全问题
       // synchronized (锁对象){
        //   放置有可能出现线程安全问题代码
        // }
        MyRunnable myRunnable1 = new MyRunnable();
        Thread th1 = new Thread(myRunnable1);
        Thread th2 = new Thread(myRunnable1);
        Thread th3 = new Thread(myRunnable1);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
        //同步代码块,用的锁对象,是任意锁对象
        //同步方法用的锁对象是this
        //静态同步方法用的锁对象是 字节码类型
        //StringBuffer
           //     StringBuilder
        //ArrayList
        //Vector
       // HashMap
       // Hashtable

多线程(JDK5之后的Lock锁的概述和使用)(了解)面试题:死锁代码

A:Lock锁的概述
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
B:Lock和ReentrantLock
void lock()
void unlock()
C:案例演示: Lock锁的使用

package day20190727.study02.面试死锁代码;

/**
 * @description: 创建两个死锁
 * @author: @李小白
 * @create: 2019-07-27 17:09
 */
public interface Objects {
        Object obj1 = new Object();
        Object obj2 = new Object();

}

package day20190727.study02.面试死锁代码;

/**
 * @description: 创建死锁的环境
 * @author: @李小白
 * @create: 2019-07-27 17:12
 */
public class Runnables extends Thread {
    boolean flag;
    public Runnables(boolean flag){
        this.flag=flag;
    }
    @Override
    public void run() {
        if (flag){
            synchronized (Objects.obj1){
                System.out.println("true------obj1执行了");
                synchronized (Objects.obj2){
                    System.out.println("true------obj2执行了");
                }
            }
        }else {
            synchronized (Objects.obj2){
                System.out.println("false------obj1执行了");
                synchronized (Objects.obj1){
                    System.out.println("false------obj2执行了");
                }
            }

        }
    }
}

package day20190727.study02.面试死锁代码;

/**
 * @description: 调用线程
 * @author: @李小白
 * @create: 2019-07-27 17:10
 */
public class Demo {
    public static void main(String[] args) {
        Runnables r1 = new Runnables(true);
        Runnables r2 = new Runnables(false);
        r1.start();
        r2.start();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值