Java高级:多线程:继承Thread类、实现Runnable接口、实现Callable接口、线程池;synchronized同步代码块、synchronized同步方法、ReentrantLock类

package com.atguigu.java;

/**多线程的创建,方式一:继承于Thread类的方式
 *
 * 1.创建一个继承于Thread类的子类
 * 2.重写Thread类中的run()方法:将创建的此线程要做的事(代码)声明在run()方法中。
 * 3.创建Thread类的子类的对象
 * 4.通过此对象调用start()方法:①:启动当前线程②:调用当前线程的run()方法。
 *
 * 例子:遍历100以内所有的偶数
 * @author 李长勇
 * @create 2022-07-03 22:16
 */
//1.创建一个继承于Thread类的子类
class MyThread extends Thread{
    @Override
//  2.重写Thread类中的run()方法:将创建的此线程要做的事声明在run()方法中。
    public void run() {
        for(int i = 0;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + i);
            }
        }

    }
}

public class ThreadTest {
    public static void main(String[] args) {
//        3.创建Thread类的子类的对象
        MyThread t = new MyThread();
//        4.通过此对象调用start()方法:①:启动当前线程②:调用当前线程的run()方法。
        t.start();

        //问题一:我们不能通过直接调用run()方法的方式启动线程

        //问题二:再启动一个线程,遍历100以内的偶数:不可以还让已经statrt的线程去执行
        //myThread.start();会抛异常
        //我们需要重新创建一个线程的对象
        MyThread t1 = new MyThread();
        t1.start();

        //如下操作仍然是在main线程中执行的。
        for(int i = 0;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
}
package com.atguigu.java;

/**
 *
 * 创建多线程的方式 2 :实现Runnable接口。
 *1.创建一个实现了Runnable接口的类
 * 2.实现类去实现Runnable中的抽象方法:run()
 * 3.创建实现类的对象
 * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
 * 5.通过Thread类的对象去调用start()方法
 *
 *
 * 比较创建线程的两种方式:
 *  开发当中:优先选择实现Runnable接口的方式
 *  原因:1.实现方式没有类的单继承性的局限性
 *        2.实现的方式更适合来处理多个线程有共享数据的情况
 *
 * 联系:Thread类本身也是实现了Runnable接口
 * 相同点:两种方式都需要重写run()方法,将线程要执行的逻辑声明在run()方法中。
 *
 * @author 李长勇
 * @create 2022-07-05 14:28
 */
//1.创建一个实现了Runnable接口的类
class MThread implements Runnable{
    //2.实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for(int i = 0;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
public class ThreadTest1 {
    public static void main(String[] args) {
        //3.创建实现类的对象
        MThread myt = new MThread();
        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(myt);
        //5.通过Thread类的对象去调用start()方法:①:启动线程,②:调用当前线程的run()方法
        t1.setName("线程1");
        t1.start();//----->调用了Runnable类型的run()方法

        //再创建一个线程:
        Thread t2 = new Thread(myt);
        t2.setName("线程2");
        t2.start();
    }
}
package com.atguigu.java;

/**
 * 测试Thread中的常用方法:
 * 1.start():启动当前线程;调用当前线程的run()方法
 * 2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法的方法体中
 * 3.currentThread():是一个静态方法,返回执行当前代码的线程
 * 4.getName():获取当前线程的名字
 * 5.setName():设置当前线程的名字
 * 6.yield():释放当前CPU的执行权
 * 7.join():在线程A中调用线程B的join方法,线程A就进入阻塞状态,直到线程B完全执行结束之后,线程A阻塞状态结束。
 * 8.stop():已经过时,当执行此方法时,强制结束当前线程
 * 9.sleep(long millitime):让当前线程”睡眠“指定的millitime毫秒,也就是处于阻塞状态
 * 10.isAlive():判断当前线程是否存活
 *
 * 线程的优先级
 * 1.
 * MAX_PRIORITY = 10
 * MIN_PRIORITY = 1
 * NORM_PRIORITY = 5
 *
 * 2.如何获取和设置当前线程的优先级
 * getPriority():获取
 * setPriority():设置
 *
 * 说明:高优先级的线程要抢占低优先级的线程,但是也只是在概率上讲,高优先级的线程高概率的情况下被执行,
 * 但是并不意味着只有当高优先级的线程执行完之后低优先级的才执行。
 *
 * @author 李长勇
 * @create 2022-07-05 0:30
 */

class HelloThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
//                try {//不能throws,因为重写方法的规则
//                    sleep(10);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                System.out.println(Thread.currentThread().getName() + Thread.currentThread().getPriority() + i);
            }
//            if(i % 20 == 0){
//                this.yield();
//            }
        }
    }
}

public class ThreadMethodTest{
    public static void main(String[] args) {
        HelloThread h1 = new HelloThread();
        h1.setName("线程一:");
        //设置分线程的优先级:
        h1.setPriority(Thread.MAX_PRIORITY);
        h1.start();
        //设置主线程的优先级:
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
        //给主线程命名:
        Thread.currentThread().setName("主线程");
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + Thread.currentThread().getPriority() + i);
            }
//            if (i == 40) {
//                try {
//                    h1.join();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
        }
//        System.out.println(h1.isAlive());
    }
}



package com.atguigu.java;

/**
 * 例子:创建三个窗口买票,总票数为100张   使用了继承Thread类的方式
 *
 * 存在线程安全的问题,需要解决!
 * @author 李长勇
 * @create 2022-07-05 10:45
 */
public class WindowTest {
    public static void main(String[] args) {

        Window t1 = new Window();
        Window t2 = new Window();
        Window t3 = new Window();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window extends Thread{
    private static int ticket = 100;
    @Override
    public void run() {
        while(true){
            if(Window.ticket > 0){
                System.out.println(getName() + ":买票" + Window.ticket);
                Window.ticket--;
            }else{
                break;
            }
        }
    }
}
package com.atguigu.java;

/**
 *
 * 例子:创建三个窗口买票,总票数为100张   使用了实现Runnable接口的方式
 *存在线程安全问题,仍需要解决
 * @author 李长勇
 * @create 2022-07-05 15:13
 */

class Window1 implements Runnable{
    private  int ticket = 100;
    @Override
    public void run() {
        while(true) {
            if (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出票号:" + ticket);
                ticket--;
            } else {
                break;
            }
        }
    }
}

public class WindowTest1 {

    public static void main(String[] args) {

        Window1 w1 = new Window1();

        Thread t1 = new Thread(w1);
        t1.setName("窗口1");
        t1.start();

        Thread t2 = new Thread(w1);
        t2.setName("窗口2");
        t2.start();

        Thread t3 = new Thread(w1);
        t3.setName("窗口3");
        t3.start();
    }
}
package com.atguigu.java;

/**
 *
 * 例子:创建三个窗口买票,总票数为100张   使用了实现Runnable接口的方式
 *存在线程安全问题,仍需要解决
 * @author 李长勇
 * @create 2022-07-05 15:13
 *
 *1.问题:卖票过程中,出现了重票、错票--->线程安全问题
 * 2.出现问题的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也来操作车票
 * 3.如何解决:当线程A在操作ticket的时候,其他线程不能参与进来,直到线程A操作完,其他线程才可以
 * 开始操作ticket,即使线程A被阻塞,也不能被改变。
 *
 * 4.在Java中通过同步机制来解决线程的安全问题。
 *
 * 方式一:同步代码块
 *
 * synchronized(同步监视器){
 *     需要被同步的代码;
 * }
 *说明:1.操作共享数据的代码,就是需要被同步的代码----不能包含代码多了,也不能少了
 *      2.共享数据:多个线程共同操作的变量/数据
 *      3.同步监视器:俗称:锁。任何一个类的对象都可以充当锁!
 *            要求:多个线程必须要共用同一把锁!
 *补充:在实现Runnable接口的实现类创建多线程中,可以考虑使用this充当锁,也就是同步监视器
 *
 * 方式二:同步方法
 *
 *
 * 5.同步的方式:解决了线程的安全问题。-----好处
 *   操作同步代码时只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低---局限性。
 *
 */

class Window1 implements Runnable{
    private  int ticket = 100;

    //位置非常重要:多个线程必须要共用同一把锁!
//    方式二:Object obj = new Object();

    @Override
    public void run() {
        while(true) {
            synchronized(this){//此时的this就是代表的:唯一的Window1对象//方式二:synchronized(obj){
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出票号:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

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

        Window1 w1 = new Window1();

        Thread t1 = new Thread(w1);
        t1.setName("窗口1");
        t1.start();

        Thread t2 = new Thread(w1);
        t2.setName("窗口2");
        t2.start();

        Thread t3 = new Thread(w1);
        t3.setName("窗口3");
        t3.start();
    }
}
package com.atguigu.java;


/**
 * 使用同步代码块的方式解决继承Thread类的方式的线程安全问题
 *
 * 例子:创建三个窗口买票,总票数为100张   使用了继承Thread类的方式
 * 存在线程安全的问题,需要解决!
 *
 * 说明:在实现Thread类创建多线程中,慎用this充当同步监视器,考虑使用当前类充当同步监视器(锁)。
 *
 *
 *
 * @author 李长勇
 * @create 2022-07-05 10:45
 */
public class WindowsTest2 {
    public static void main(String[] args) {

        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window2 extends Thread{
    private static int ticket = 100;

    //锁,也就是同步监视器只能共同使用一个(必须加static)
    private static Object obj = new Object();

    @Override
    public void run() {
        while(true){
//            synchronized(Window2.class){//是可行的!
            synchronized(obj) {//这个继承类就不能用this(不唯一)(当前类的对象)来当作锁了
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if (Window2.ticket > 0) {
                    System.out.println(getName() + ":买票" + Window2.ticket);
                    Window2.ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

package com.atguigu.java;

/**
 * 使用同步方法的方式解决继承Thread类的方式的线程安全问题
 *
 * 例子:创建三个窗口买票,总票数为100张   使用了继承Thread类的方式
 * 存在线程安全的问题,需要解决!
 *
 * 说明:在实现Thread类创建多线程中,慎用this充当同步监视器,考虑使用当前类充当同步监视器(锁)。
 *
 * 关于同步方法的总结:
 *
 * 1.同步方法仍然涉及到同步监视器,只是没有显示的声明
 * 2.非静态方法,同步监视器是this
 *   静态方法的同步监视器是:当前类本身
 *
 *
 * @author 李长勇
 * @create 2022-07-05 10:45
 */
public class WindowsTest4 {
    public static void main(String[] args) {

        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window4 extends Thread{
    private static int ticket = 100;
    @Override
    public void run() {
        while(true){
            show();
        }
    }

    private static synchronized void show() {//同步监视器是唯一的了:静态里面:windows.class
        try {
            sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (Window4.ticket > 0) {
            System.out.println(Thread.currentThread().getName() + ":买票" + Window4.ticket);
            Window4.ticket--;
        }
    }
}
package com.atguigu.java1;

import java.util.concurrent.locks.ReentrantLock;

import static java.lang.Thread.sleep;

/**
 * 解决线程安全问题方式三:Lock锁----JDK5.0新增
 *
 *1.面试题:synchronized 和 lock 的异同点?
 * 同:都可以解决线程安全问题
 * 异同点:synchronized 机制执行完相应的代码机制之后自动的释放同步监视器
 * lock需要手动的启动(lock())同步,同时必须手动的结束同步(unlock())
 *
 * 1.面试题:解决线程安全的有几种方法? 3种
 *
 * @author 李长勇
 * @create 2022-07-08 14:17
 */

class Window implements Runnable{

    private ReentrantLock lock = new ReentrantLock(true);//公平锁

    private int ticket = 100;

    @Override
    public void run() {
        while(true){
            lock.lock();
            try{
                if(ticket > 0){

                    try {
                        sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally {
                lock.unlock();
            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window w =  new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.atguigu.java1;

/**
 *
 * 演示线程的死锁
 *
 * 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,
 * 都在等待对方放弃自己需要的同步资源,就形成了死锁
 * 2.出现死锁后,不会出现异常等,只是线程全部阻塞状态
 * 一定避免出现死锁问题
 *
 *
 * @author 李长勇
 * @create 2022-07-08 13:49
 */
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread(){
            @Override
            public void run() {
                synchronized(s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }

            }
        }.start();

        new Thread(new Runnable(){
            @Override
            public void run() {
                synchronized(s2){
                    s1.append("c");
                    s2.append("3");
                    synchronized(s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}
package com.atguigu.java2;

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

/**
 * 创建线程的方式三:实现Callable接口的方式----JDK5.0新增的
 *
 * 如何理解实现Callable接口的方式创多线程比实现Runnable接口更加强大?
 * 1.call()方法可以有返回值的
 * 2.call()方法可以抛出异常,被外面的操作捕获,获取异常的信息
 * 3.call()是支持泛型的。
 *
 *
 *
 *
 *
 * @author 李长勇
 * @create 2022-07-08 17:04
 */

//1.创建一个实现了Callable接口的实现类
class NumThread implements Callable <Integer>{
    //2.实现Call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Integer call() {
        int sum = 0;
        for(int i = 0; i <= 100; i++){
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为参数传入FutureTask构造器中,去创建FutureTask类的对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
        //5.将FutureTask类的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法
        new Thread(futureTask).start();

        try {
            //6.获取Callable中call方法的返回值:
            //get()方法的返回值即为FutureTask构造器参数Callable实现类重写的Call方法的返回值。
            Integer sum = futureTask.get();
            System.out.println("总和为:"  + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}
package com.atguigu.java2;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 *
 * 创建线程的方式4:线程池
 *
 * 好处:
 * 1.提高响应速度(减少了创建新线程的时间)
 * 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
 * 3.便于线程管理:
 *      corePoolSize:核心池的大小
 *      maximumPoolSize:最大线程数
 *      keepAliveTime:线程没有任务时最多保持多长时间后会终止
 *
 *
 * 面试题:创建多线程有几种方式?
 *   --4种
 *
 * @author 李长勇
 * @create 2022-07-08 19:20
 */

class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++){
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++){
            if (i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args) {
        //1.提供指定线程数量的线程池,
        ExecutorService service = Executors.newFixedThreadPool(10);// 创建线程池service:里面有10个线程

        //设置线程池的属性:
        System.out.println(service.getClass());
        //强转
        ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;

        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime(24524L,service1);


        //2.执行指定的线程的操作。需要提供实现Runnable或者Callable接口的实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable接口
        service.execute(new NumberThread1());//适合适用于Runnable接口

//        service.submit(Callable callable);//适合适用于Callable接口

        //3.关闭连接池:
        service.shutdown();
    }
}
package com.atguigu.java2;

/**
 * 线程通信:两个线程交替打印1-100
 *
 * 涉及到的三个方法:
 * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
 * notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait优先唤醒优先级高的线程
 * notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
 *
 * 说明:
 * 1.wait() notify() notifyAll()只能出现在同步代码块或者同步方法中
 * 2.wait() notify() notifyAll()这三个方法的调用者必须是同步监视器
 *  否则会出现异常
 * 3.wait() notify() notifyAll()是定义在Object()类中的。
 *
 *
 * @author 李长勇
 * @create 2022-07-08 15:26
 */

class Number implements Runnable{
    private int number = 1;
    @Override
    public void run() {
        while(true) {
            synchronized (this) {
                notify();
                if (number <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                        number++;
                }else{
                    break;
                }
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class CommunicationTest {
    public static void main(String[] args) {
        Number n = new Number();

        Thread t1 = new Thread(n);
        Thread t2 = new Thread(n);

        t1.setName("线程1");
        t2.setName("线程2");

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

package com.atguigu.exer;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 银行有一个账户
 * 有两个储户分别向同一个账户存3000,每次存1000,存三次,每次存完打印余额
 * 1.多线程:两个储户线程
 * 2.共享数据:账户余额
 * 3.有线程安全问题
 * 4、同步机制解决线程安全问题:三种方式
 * @author 李长勇
 * @create 2022-07-08 14:39
 */
public class AccountTest {
    public static void main(String[] args) {
        Customer c1 = new Customer();
        Customer c2 = new Customer();
        c1.setName("储户1");
        c2.setName("储户2");
        c1.start();
        c2.start();
    }
}

class Count{
    private static int balance = 0;
    public static void disposit() {
        balance += 1000;
        System.out.println(Thread.currentThread().getName() + "存钱成功,余额为" + balance);
    }
}

class Customer extends Thread{
    private static Count count = new Count();
    private static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        try{
            lock.lock();//因为是使用的继承的方式实现多线程,所以lock必须是静态的
            for (int i = 0; i < 3; i++) {
                count.disposit();
            }
        }finally{
            lock.unlock();
        }
    }
}

package com.atguigu.exer;

/**
 * 生产者与消费者的生产模式!
 * @author 李长勇
 * @create 2022-07-08 16:10
 */
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        Consumer c1 = new Consumer(clerk);

        p1.setName("消费者1");
        c1.setName("生产者1");

        p1.start();
        c1.start();
    }
}
class Clerk{
    private int productCount = 0;
    //生产产品
    public synchronized void pruduceProduct() {
        if(productCount < 20){
            productCount ++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //消费产品
    public synchronized void consumeProduct(){
        if(productCount > 0){
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount--;

            notify();

        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer extends Thread{

    private Clerk clerk;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName()  + "生产者开始生产");
        while(true){

            try {
                sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.pruduceProduct();
        }

    }
}

class Consumer extends Thread{

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName()  + "消费者开始消费");
        while(true){
            clerk.consumeProduct();
        }

    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值