用代码介绍多线程(java基础篇)

java多线程介绍


简单的说:多线程就是同一时间段内同时做多件事情。效率大大提高。当然把如果时间段看成很小的时间单位时就不存在多线程了。
下面重点介绍java怎么使用多线程。

开启线程方法一继承Thread类

直接看代码。注意main方法就是一个线程。再开一个就是两个线程了。在一个线程里开启一个线程。称子线程。

class Demo extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <5 ; i++) {
            System.out.println("我在听歌。。。。");
        }
    }
}
public class Main {
    public static void main(String[] args) {

        //demo继承了Thread
        Demo demo = new Demo();
        //运行子线程。start()是Thread的方法
        demo.start();
        for (int i = 0; i <5 ; i++) {
            System.out.println("我在学习java多线程");
        }
        //结果
        //我在学习java多线程
        //我在学习java多线程
        //我在学习java多线程
        //我在学习java多线程
        //我在学习java多线程
        //我在听歌。。。。
        //我在听歌。。。。
        //我在听歌。。。。
        //我在听歌。。。。
        //我在听歌。。。。
    }
}

开启线程方式二实现Runnable接口

推荐使用方法二。因为java是单继承,多实现。实现同一个功能能不用继承最好不要用。

class Demo implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <5 ; i++) {
            System.out.println("我在听歌。。。。");
        }
    }
}
public class Main {
    public static void main(String[] args) {

        //demo继承了Thread
        Demo demo = new Demo();
        //创建一个子线程.代理模式
        Thread thread = new Thread(demo);
        //运行子线程
        thread.start();
        for (int i = 0; i <5 ; i++) {
            System.out.println("我在学习java多线程");
        }
        //结果
        //我在学习java多线程
        //我在学习java多线程
        //我在学习java多线程
        //我在学习java多线程
        //我在学习java多线程
        //我在听歌。。。。
        //我在听歌。。。。
        //我在听歌。。。。
        //我在听歌。。。。
        //我在听歌。。。。
    }
}

开启线程方法三实现Callable<?>接口

此方法初学者建议跳过。而且阿里巴巴也建议不用Executors创建线程池了。

//String代表要返回的类型
class Demo implements Callable<String> {
    //前面两种方法创建用run()无法抛异常返回值只能是void
    @Override
    public String call() throws Exception {
            System.out.println("我在听歌。。。");
        return "我回来了";
    }
}
public class Main {
    public static void main(String[] args) {
        Demo demo = new Demo();
        //创建一个线程池 参数是可以接纳多少个线程
        ExecutorService service = Executors.newFixedThreadPool(2);
        //加入线程1
        Future<String> submit1 = service.submit(demo);
        //加入线程2
        Future<String> submit2 = service.submit(demo);
        try {
            String value1 = submit1.get();
            //获取线程1返回的值
            System.out.println("我是线程1:"+value1);
            String value2 = submit2.get();
            //获取线程2返回的值
            System.out.println("我是线程2:"+value2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //关闭线程
          service.shutdown();
        //结果
        //我在听歌。。。
        //我在听歌。。。
        //我是线程1:我回来了
        //我是线程2:我回来了
    }
}

用Lambda写法介绍

Lambda针对一个接口仅有一个方法时可以简化匿名内部类的写法

interface Demo{
    String say(int a);
}
public class Main {
    public static void main(String[] args) {

          Demo demo1 = new Demo(){
              @Override
              public String say(int a) {
                  System.out.println("我是正常匿名类实例化->a="+a);
                  return "demo1正常匿名类返回值";
              }
          };
        System.out.println(demo1.say(1));
        //结果
        //我是正常匿名类实例化->a=1
        //demo1正常匿名类返回值
        Demo demo2 = (int a)->{
            System.out.println("我是Lambda简化的匿名内部类->a="+a);
            return "demo2Lambda简化的匿名返回值";
        };
        System.out.println(demo2.say(2));
        //结果
        //我是Lambda简化的匿名内部类->a=2
        //demo2Lambda简化的匿名返回值


        //当参数只有一个时省略参数类型如下
        Demo demo3 = a->{
            System.out.println("我是Lambda简化的匿名内部类->a="+a);
            return "demo3Lambda简化的匿名返回值";
        };
        System.out.println(demo3.say(3));
        //结果
        //我是Lambda简化的匿名内部类->a=3
        //demo3Lambda简化的匿名返回值

        //当方法的就一行是可以省略花括号如(如果是return return 关键字也要去掉)
        Demo demo4 = a-> "demo4Lambda简化的匿名返回值a="+a;
        System.out.println(demo4.say(4));
        //结果
        //demo4Lambda简化的匿名返回值a=4
    }
}

用Lambda简化开启线程方式二

public class Main {
    public static void main(String[] args) {

        //普通匿名内部类写法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是普通写法的匿名内部类");
            }
        }).start();
        //简化一
        new Thread(()->{
            System.out.println("我是Lambda简化的匿名内部类");
        }).start();
        //简化二
        new Thread(()-> System.out.println("我是Lambda最简化的匿名内部类")).start();
        //结果
        //我是普通写法的匿名内部类
        //我是Lambda简化的匿名内部类
        //我是Lambda最简化的匿名内部类
    }
}

Thread.yield()礼让线程

class Demo implements Runnable{
    @Override
    public void run() {
        //Thread.currentThread().getName()获取当前线程的名字
        System.out.println(Thread.currentThread().getName()+"=Start========");
        //礼让线程。礼让后公平竞争。有可礼让后还是调用自己。记住是公平竞争
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"=end========");
    }
}
public class Main {
    public static void main(String[] args) {
        Demo demo = new Demo();
        Thread threadOne = new Thread(demo);
        threadOne.setName("线程一");//设置线程的名字。要在启动前
        threadOne.start();//启动
        Thread threadTow = new Thread(demo);
        threadTow.setName("线程二");
        threadTow.start();
        //多运行几次有不同的结果
        //结果1
        //线程二=Start========
        //线程一=Start========
        //线程二=end========
        //线程一=end========
        //结果2-->这个结果就是礼让后还是调用自身。(公平竞争)
        //线程一=Start========
        //线程一=end========
        //线程二=Start========
        //线程二=end========
    }
}

Thread.sleep();抱着资源睡觉

public class Main {
    public static void main(String[] args) {
        new Thread(()->{
            try {
                //单位是毫秒这里是1秒
                //抱着资源睡觉。没有释放资源。只是单纯的睡了一秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("一秒后输出我");
        }).start();
    }
}

thread.join();线程插队

thread.join();出现在哪个线程就往哪个线程插入

public class Main {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("===========我是子线程"+i);
            }
        });
        thread.start();
        for (int i = 0; i <10 ; i++) {
            if(i==5){
                try {
                    //合并线程。将子线程插入主线程中。先走完子线程再走主线程
                    //相当于把子线程的代码复制到这。
                    //可能插队形容起来更加贴切
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("我是主线程"+i);
        }
        //结果->主线程0到4都要是大家公平竞争。谁快输出谁。主线程4后子线程就插队了。
        //我是主线程0
        //我是主线程1
        //我是主线程2
        //===========我是子线程0
        //===========我是子线程1
        //我是主线程3
        //我是主线程4
        //===========我是子线程2
        //===========我是子线程3
        //===========我是子线程4
        //我是主线程5
        //我是主线程6
        //我是主线程7
        //我是主线程8
        //我是主线程9
    }
}

线程的优先级Priority

优先级只是代表调用的概率。并不保证执行的顺序(可以设置1到10)默认是5

class Demo implements Runnable{
    @Override
    public void run() {
        //获取当前线程的名字
        String name = Thread.currentThread().getName();
        //获取当前线程的优先级
        int priority = Thread.currentThread().getPriority();
        //输出
        System.out.println(name+"->优先级是:"+priority);
    }
}
public class Main {
    public static void main(String[] args) {
        Demo demo = new Demo();
        //构造方法也可以设置线程的名字
        Thread t1 = new Thread(demo,"t1");

        Thread t2 = new Thread(demo,"t2");
        Thread t3 = new Thread(demo,"t3");
        Thread t4 = new Thread(demo,"t4");
        Thread t5 = new Thread(demo,"t5");
        //设置优先级-》Thread.MAX_PRIORITY是个常量。也可以填1到10;

         t1.setPriority(Thread.MIN_PRIORITY);//Thread.MAX_PRIORITY==1
        t2.setPriority(Thread.NORM_PRIORITY);//Thread.MAX_PRIORITY==5
        t3.setPriority(Thread.MAX_PRIORITY);//Thread.MAX_PRIORITY==10
        t4.setPriority(3);//Thread.MAX_PRIORITY==10
        t5.setPriority(6);//Thread.MAX_PRIORITY==10

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        //结果 可以看到优先级高的不一定最先运行
        //t5->优先级是:6
        //t4->优先级是:3
        //t2->优先级是:5
        //t3->优先级是:10
        //t1->优先级是:1

    }
}

线程的状态Thread.State

NEW->代表刚刚分配地址
RUNNABLE->启动状态
BLOCKED->一个线程因为等待临界区的锁被阻塞产生的状态
WAITING->一个线程进入了锁,但是需要等待其他线程执行某些操作。时间不确定
TIMED_WAITING->一个线程进入了锁,需要等待其他线程执行某些操作。时间确定
TERMINATED->结束状态

 public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("---执行了--");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //获取线程的状态t前面线程的实例化
        Thread.State state = t.getState();
        System.out.println(state);//NEW->分配空间
        t.start();
        System.out.println(t.getState());//RUNNABLE->启动
        try {
            Thread.sleep(500);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(t.getState());//TIMED_WAITING->时间堵塞
        try {
            Thread.sleep(1000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(t.getState());//TERMINATED->停止了
    }

线程不安全例子

模拟去12306抢票

class Demo implements Runnable {
    //总票数是十张
    private Integer count = 10;
    @Override
    public void run() {
        //一人要抢十张票
        for (int i = 0; i < 10; i++) {
            //如果没票返回false
            boolean b = test01();
            if (!b) {
                //没票就退出
                break;
            }
        }
    }
    private boolean test01() {
            try {
                //模拟网络延时。
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //这里要抢一张票。如果没有了则return false
            if ((count-1) < 0) {
                System.out.println("票没了");
                return false;
            }
            //上面没返回证明有票
            System.out.println(Thread.currentThread().getName() + "抢到票,票号:" + count);
            //票数减一
            count--;
            //抢到票

            return true;

    }
}
public class Main {
    public static void main(String[] args) {
        Demo demo = new Demo();
        //A过来抢票
        new Thread(demo,"A").start();
        //B也要抢
        new Thread(demo,"B").start();
        //结果
        //A抢到票,票号:10
        //B抢到票,票号:10
        //A抢到票,票号:8
        //B抢到票,票号:8
        //A抢到票,票号:6
        //B抢到票,票号:6
        //B抢到票,票号:4
        //A抢到票,票号:4
        //A抢到票,票号:2
        //B抢到票,票号:2
        //票没了
        //票没了
    }
}

可以看到票号被重复抢到了。重复的原因是因为两个线程相互抢夺资源。
如A线程抢到票了还没来的及去修改剩余票数。B线程就来这时B线程将获取和A线程一样的票数号。导致安全。

synchronized解决线程安全

synchronized同步锁
注意->锁的对象地址不能改变。会变的对象锁也没用。可以看到test02就是错误例子。
可以修饰方法可以 如private synchronized boolean test01();还可以做同步块代码test01就是

class Demo implements Runnable {
    //总票数是十张
    private Integer count = 10;
    @Override
    public void run() {
        //一人要抢十张票
        for (int i = 0; i < 10; i++) {
            //如果没票返回false
            boolean b = test01();
            if (!b) {
                //没票就退出
                break;
            }
        }
    }
    private  boolean test01() {
        //没票直接返回,不必到下面排队等锁释放。大大提高效率
        if((count-1)<0){
            return false;
        }
        //注意参数要锁对->可以看test02就锁错了对象
        //参数可以是对象,可以是Class
        synchronized (this){
            try {
                //模拟网络延时。
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //这里要抢一张票。如果没有了则return false
            if ((count-1) < 0) {
                System.out.println("票没了");
                return false;
            }
            //上面没返回证明有票
            System.out.println(Thread.currentThread().getName() + "抢到票,票号:" + count);
            //票数减一
            count--;
            //抢到票
            return true;
        }
    }

    private  boolean test02() {
        //这里锁的是count。可count对象在变。锁不住
        synchronized (count){
            try {
                //模拟网络延时。
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //这里要抢一张票。如果没有了则return false
            if ((count-1) < 0) {
                System.out.println("票没了");
                return false;
            }
            //上面没返回证明有票
            System.out.println(Thread.currentThread().getName() + "抢到票,票号:" + count);
            //票数减一
            count--;
            //抢到票
            return true;
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Demo demo = new Demo();
        //A过来抢票
        new Thread(demo,"A").start();
        //B也要抢
        new Thread(demo,"B").start();
        //结果
        //A抢到票,票号:10
        //B抢到票,票号:9
        //A抢到票,票号:8
        //A抢到票,票号:7
        //A抢到票,票号:6
        //B抢到票,票号:5
        //B抢到票,票号:4
        //B抢到票,票号:3
        //A抢到票,票号:2
        //A抢到票,票号:1
        //票没了
        //票没了
    }
}

Wait和Notify和thread.setDaemon(true);

Wait是让线程等待 Notify唤醒等待的线程
模拟面包铺。生产包子和顾客来买包子。
thread.setDaemon(true);//开启守护线程守护线程就是主线程结束后其自动结束。用于收尾工作。包子的例子中都没顾客线程都没看。生产的线程也没必要存在

//缓存区
class SynContainer{
    private Queue<String> queue;//用来存放生产的包子
    //构造器实例化队列
    public SynContainer(Queue<String> queue) {
        this.queue = queue;
    }

   public synchronized void pusp(String name){
       int size = queue.size();
       //这里设定了只能生产10个包子。达十个要等顾客消费再生产
       if (size>=10){
           System.out.println("等待消费");
           try {
               //到达十个了。停止生产
               //wait();让其线程等待。等待被唤醒
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       //生产了就往队列里压
       queue.offer(name);
       System.out.println("生产了"+name);
       //生产了证明有包子卖了
       //唤醒等待的线程。有可能客户正在等待中
      this.notifyAll();
   }
   public synchronized String pop(){
        //顾客来买包子。发现没有只能等待
       if(queue.size()==0){
           System.out.println("等待生产");
           try {
               //让其线程等待
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       //买一个包子就出队一个
       String value = queue.poll();
       //买了一个包子。就不可能还有十个
       //通知生产者生产
       this.notifyAll();
       //返回购买的包子
       return value;
   }
}
//生产者
class  Productor implements Runnable{

    private SynContainer synContainer;

    public Productor(SynContainer synContainer) {
        this.synContainer = synContainer;
    }

    @Override
    public void run() {
        //最多去生产20个。生产十个就得停下等顾客购买
        for (int i = 1; i <= 20; i++) {

             synContainer.pusp("包子"+i+"号");
        }
    }
}
//消费者
class Consumer implements Runnable{
    private SynContainer synContainer;

    public Consumer(SynContainer synContainer) {
        this.synContainer = synContainer;
    }

    @Override
    public void run() {
        //顾客要买一百个包子
        for (int i = 0; i <15 ; i++) {
            String name = synContainer.pop();
            System.out.println("获得"+name);
        }
    }

}
public class WaitAndNotifyDemo01 {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer(new LinkedList<>());
        Thread thread = new Thread(new Productor(synContainer));
        thread.setDaemon(true);//开启守护线程
        thread.start();
        new Thread(new Consumer(synContainer)).start();

        //结果
        //等待生产
        //生产了包子1号
        //生产了包子2号
        //生产了包子3号
        //生产了包子4号
        //生产了包子5号
        //生产了包子6号
        //生产了包子7号
        //生产了包子8号
        //生产了包子9号
        //生产了包子10号
        //等待消费
        //生产了包子11号
        //等待消费
        //获得包子1号
        //获得包子2号
        //生产了包子12号
        //等待消费
        //获得包子3号
        //获得包子4号
        //获得包子5号
        //生产了包子13号
        //生产了包子14号
        //生产了包子15号
        //等待消费
        //获得包子6号
        //生产了包子16号
        //等待消费
        //获得包子7号
        //获得包子8号
        //获得包子9号
        //获得包子10号
        //获得包子11号
        //生产了包子17号
        //生产了包子18号
        //生产了包子19号
        //生产了包子20号
        //获得包子12号
        //获得包子13号
        //获得包子14号
        //获得包子15号
    }
}

代理模式

代理模式就是不动原来的代码。为其增加功能。在不动源代码的前提下。想执行其方法前打印日志就需要用到该模式。spring的Aop就是典型的例子

interface Proxy{
    void happyPaly();
}
class You implements Proxy{

    @Override
    public void happyPaly() {
        System.out.println("是我在玩遥控车");
    }
}
class WeddingCompany implements Proxy{
    private Proxy proxy;
   WeddingCompany(Proxy proxy){
       this.proxy=proxy;
   }
    @Override
    public void happyPaly() {
        beforHappyPaly();
       proxy.happyPaly();
        afterHappyPaly();
    }
    private void  beforHappyPaly(){
        System.out.println("代理帮遥控车充电了。。。。。");
    }
    private void afterHappyPaly(){
        System.out.println("代理帮说遥控车没电了。。。。");
    }
}
public class ProxyDemo {
    public static void main(String[] args) {
        WeddingCompany weddingCompany = new WeddingCompany(new You());
        weddingCompany.happyPaly();
        //结果
        //代理帮遥控车充电了。。。。。
        //是我在玩遥控车
        //代理帮说遥控车没电了。。。。
        //这里可以看出new Thread(对象).start();就是代理对象
    }
}

注意代理和修饰写法好像但是两种设计模式不一样

指令重排-单例模式例子–volatile

指令重排就是jvm为了优化代码。而不影响代码本身的前提下对代码进行排列。可是在多线程就会出事。
volatile->保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
volatile->禁止进行指令重排序。(实现有序性)
volatile-> 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。

class SingletonDubleChecked{
    //考虑到new SingletonDubleChecked();时可能发生指令重排
    private static volatile SingletonDubleChecked singletonDubleChecked;
    private SingletonDubleChecked(){}
    public static SingletonDubleChecked getSingleObj(){
        if(singletonDubleChecked==null){
            synchronized (SingletonDubleChecked.class){
                if(singletonDubleChecked==null){
                    singletonDubleChecked = new SingletonDubleChecked();
                    //如果不加volatile修饰
                    //singletonDubleChecked = new SingletonDubleChecked();
                    //第一步->new 为其分配权限
                    //第二步->SingletonDubleChecked();初始化对象
                    //第三不->对象赋值
                    //正常来说是1->2->3
                    //指令重排的话可能是1->3->2
                    //132的话在多线程。可能会获取到null值。因为还没赋值就往下了
                    System.out.println("初始化");
                }
            }
        }
        return singletonDubleChecked;
    }
}

public class DoubleCheckedSingleton {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                SingletonDubleChecked.getSingleObj();
            }).start();
        }
    }

}

ThreadLocal

ThreadLocal为每个线程都开辟一个独立的空间。使得多线程数据可以相互不干扰
官方建议用 private static 来修饰ThreadLocal
get/set/initialValue

public class Main {
    //private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()-> 200);给初始化值
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        threadLocal.set(100);
        new Thread(()->{
           threadLocal.set(50);
            System.out.println("子线程:"+threadLocal.get());
        }).start();
        try {
            //等待一秒
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程"+threadLocal.get());
        //结果
        //子线程:50
        //主线程100
        //可以看到主线程和子线程数据不干扰
    }
}

CAS

Compare and Swap,即比较再交换。
jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。
CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快。而且CAS避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了。

import java.util.concurrent.atomic.AtomicInteger;

public class CAS {
    //Atomic原子性
    private static AtomicInteger count = new AtomicInteger();
    public static void main(String[] args) {
        count.set(5);
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                for (int j = 0; j < 10; j++) {
                    //减一
                    int c = count.decrementAndGet();
                    if(c<1){
                       // System.out.println("抢完了");
                        return ;
                    }
                    System.out.println(Thread.currentThread().getName()+"抢到了"+c);
                }
            }).start();
        }
        //结果
        //Thread-2抢到了3
        //Thread-3抢到了1
        //Thread-1抢到了2
        //Thread-0抢到了4
    }
}

TimerTask

设一个时间任务

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

class MyTask extends TimerTask {
    private Timer timer;
    MyTask(Timer timer){
          this.timer=timer;
    }
    @Override
    public void run() {
        System.out.println("时间到我自然启动了。。。。。");
        Date date = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dateStr = dateFormat.format(date);
        System.out.println("启动时间"+dateStr);
        //执行后关闭
        timer.cancel();
    }
}
public class TimerDemo01 {
    public static void main(String[] args) {
        //Timer(true)//开启守护线程
        Timer timer = new Timer();
        try {
            Date parse = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2025-08-08 22:12:00");
            timer.schedule(new MyTask(timer),parse);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

注意

public class Main {
    public static void main(String[] args) {
       int i=1;
       int arr[] = new int[5];
        new Thread(()->{
            //i=2;是错的
            int a = i;
            a = 2;//这样就可以
            System.out.println(a);
            //可以看出只要不修改i的地址就可以
            //i--;也是错的。修改了原来的地址
            //集合数组可以直接赋值操作因为没改变其地址
            arr[0] = 1;//这个是对的
        }).start();
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值