线程中的方法补充、线程案例、线程池、Callable、定时器、正则表达式

线程中的方法补充

static yield() :向调度程序提示当前线程愿意放弃其当前对处理器的使用。

​ 一旦执行此方法 , 就释放CPU的执行权

注意 : yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。

public static void yield()

final void join() : 在线程A中通过线程B调用join() , 意味着线程A进入阻塞状态 , 直到线程B执行结束 , 线程A才结束阻塞状态 , 继续执行.

public final void join() throws InterruptedException 
public final void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。 
public final void join(long millis, int nanos) 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。 

final boolean isAlive() : 测试此线程是否存活。如果一个线程已经启动并且还没有死亡,那么它就是存活的。

public final boolean isAlive()

线程的优先级

​ 每个线程都有一定的优先级,同优先级线程组成先进先出队列(先到先服务),使用分时调度策略。优先级高的线程采用抢占式策略,获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。

  • Thread类的三个优先级常量:

    • MAX_PRIORITY(10):最高优先级
    • MIN _PRIORITY (1):最低优先级
    • NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
  • getPriority() : 获取线程的优先级 , 返回线程的优先级

    public final int getPriority()
    
  • setPriority( int newPriority ) : 更改此线程的优先级。 范围[1,10]

    参数 : newPriority - 设置该线程的优级

    public final void setPriority(int newPriority)
    

等待唤醒

1.等待唤醒案例分析(线程之间的通信)

  • 要求 : 一个线程生产包子 , 一个线程消费包子 , 但是不能连续生产 , 也不能连续消费 , 即生产和消费两个线程为互斥的
  • 问题1 : 怎么表示生产包子 , 怎么表示消费包子 ?
    • 如果是生产包子 , 则用count++ , 然后输出count
    • 如果是消费包子 , 则之间输出count
  • 问题2 : 怎么证明还有没有包子 ?
    • 如果flag = true , 证明有包子 , 就要消费包子
    • 如果flag = false , 证明没有包子了 , 那么就要开始生产包子了
  • 问题3 : 如何防止生产到一半 , CPU切换到消费线程去了 ?
    • 给正在执行的线程加锁
  • 问题4 : 即使加锁 , 也不能保证生产一个就消费一个 , 哪种方式可以生产和连续一次执行?
    • wait、notify、notifyAll
对生产者Product进行分析
    1.如果flag = true,证明此时有包子,则不需要生产,所以生产线程wait
    2.否则,flag = false,证明此时没有包子,则需要开始生产了,此时count++,然后开始生产包子
    3.生产完以后,应该通知消费线程notify
对消费者Consumer进行分析
    1.如果flag = false,则证明此时没有包子了,所以消费者不能消费,需要wait消费线程
    2.反之,flag = true,则证明此时有包子,那么消费线程开始工作
    3.消费完以后,应该通知生产线程notify
包子铺需要准备两个成员变量
    flag来表明是否还有存货
    count来标志包子数量

2.等待唤醒案例实现

public class BaoZiPu {
    private int count;
    private boolean flag;

    public void getCount() {
        System.out.println("消费了第"+count+"个包子");
    }

    public void setCount() {
        count++;
        System.out.println("生产了第"+count+"个包子");
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public BaoZiPu(int count, boolean flag) {
        this.count = count;
        this.flag = flag;
    }

    public BaoZiPu() {
    }
}
public class Product implements Runnable{
    private BaoZiPu baoZiPu;

    public Product(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    public Product() {
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (baoZiPu){
                if(baoZiPu.isFlag()==true){
                    try {
                        baoZiPu.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                baoZiPu.setCount();
                baoZiPu.setFlag(true);
                baoZiPu.notify();
            }
        }
    }
}
public class Consumer implements Runnable{
    private BaoZiPu baoZiPu;
    public Consumer() {
    }

    public Consumer(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    public BaoZiPu getBaoZiPu() {
        return baoZiPu;
    }

    public void setBaoZiPu(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (baoZiPu){
                if(baoZiPu.isFlag()==false){
                    try {
                        baoZiPu.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                baoZiPu.getCount();
                baoZiPu.setFlag(false);
                baoZiPu.notify();
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        BaoZiPu baoZiPu = new BaoZiPu();
        Product product = new Product(baoZiPu);
        Consumer consumer = new Consumer(baoZiPu);

        Thread t1 = new Thread(product);
        Thread t2 = new Thread(consumer);

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

3.用同步方法改造等待唤醒案例

public class BaoZiPu {
    private int count;
    private boolean flag;

    public synchronized void getCount() {
        if(flag == false){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        System.out.println("消费了第"+count+"个包子");

        flag = false;
        this.notify();
    }

    public synchronized void setCount() {
        if(flag == true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        count++;
        System.out.println("生产了第"+count+"个包子");

        flag = true;
        this.notify();
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public BaoZiPu(int count, boolean flag) {
        this.count = count;
        this.flag = flag;
    }

    public BaoZiPu() {
    }
}
public class Product implements Runnable{
    private BaoZiPu baoZiPu;

    public Product(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    public Product() {
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            baoZiPu.setCount();
        }
    }
}
public class Consumer implements Runnable{
    private BaoZiPu baoZiPu;
    public Consumer() {
    }

    public Consumer(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    public BaoZiPu getBaoZiPu() {
        return baoZiPu;
    }

    public void setBaoZiPu(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            baoZiPu.getCount();
        }
    }
}
public class Test {
    public static void main(String[] args) {
        BaoZiPu baoZiPu = new BaoZiPu();
        Product product = new Product(baoZiPu);
        Consumer consumer = new Consumer(baoZiPu);

        Thread t1 = new Thread(product);
        Thread t2 = new Thread(consumer);

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

多等待多唤醒

1.解决多生产多消费问题(if改为while,将notify改为notifyAll)

public class BaoZiPu {
    private int count;
    private boolean flag;

    public synchronized void getCount() {
        while (flag == false){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        System.out.println("消费了第"+count+"个包子");

        flag = false;
        this.notifyAll();
    }

    public synchronized void setCount() {
        while (flag == true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        count++;
        System.out.println("生产了第"+count+"个包子");

        flag = true;
        this.notifyAll();
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public BaoZiPu(int count, boolean flag) {
        this.count = count;
        this.flag = flag;
    }

    public BaoZiPu() {
    }
}
public class Product implements Runnable{
    private BaoZiPu baoZiPu;

    public Product(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    public Product() {
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            baoZiPu.setCount();
        }
    }
}
public class Consumer implements Runnable{
    private BaoZiPu baoZiPu;
    public Consumer() {
    }

    public Consumer(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    public BaoZiPu getBaoZiPu() {
        return baoZiPu;
    }

    public void setBaoZiPu(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            baoZiPu.getCount();
        }
    }
}
public class Test {
    public static void main(String[] args) {
        BaoZiPu baoZiPu = new BaoZiPu();
        Product product = new Product(baoZiPu);
        Consumer consumer = new Consumer(baoZiPu);

        Thread t1 = new Thread(product);
        Thread t2 = new Thread(product);
        Thread t3 = new Thread(product);
        Thread t4 = new Thread(consumer);
        Thread t5 = new Thread(consumer);
        Thread t6 = new Thread(consumer);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
    }
}

2.解决多生产多消费问题(if改为if_else,将notify改为notifyAll)

只需要修改BaoZiPu中的get/set业务代码 , 对进入if 的生产者或消费者被wait()之后 , 当cpu再次分配给他们时 , 需要进行重写判断 , 来继续执行 , 此时wait() 以及执行完毕 , 不再执行else中的语句 , 会重新进行抢锁 .

public class BaoZiPu {
    private int count;
    private boolean flag;

    public synchronized void getCount() {
        if(flag == false){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }else{
            System.out.println("消费了第"+count+"个包子");
            flag = false;
            this.notifyAll();
        }
    }

    public synchronized void setCount() {
        if(flag == true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }else{
            count++;
            System.out.println("生产了第"+count+"个包子");
            flag = true;
            this.notifyAll();
        }

    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public BaoZiPu(int count, boolean flag) {
        this.count = count;
        this.flag = flag;
    }

    public BaoZiPu() {
    }
}

Lock锁

1.Lock对象的介绍和基本使用

1.概述:是一个接口,需要new实现类对象
2.实现类:ReentrantLock
3.方法:
  lock()获取锁
  unlock()释放锁
public class Ticket implements Runnable {
    int ticket = 50;

    //首先获取lock对象
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100L);
                //获取锁
                lock.lock();
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName()+"抢到了第"+ticket+"张票");
                    ticket--;
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                //释放锁
                lock.unlock();
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket,"张三");
        Thread t2 = new Thread(ticket,"李四");
        Thread t3 = new Thread(ticket,"王五");
        t1.start();
        t2.start();
        t3.start();
    }
}

2.Lock和synchronized区别

2.1Lock
1.注意:
  a.Lock锁属于轻量级锁,底层实现原理为"乐观锁"(CAS机制 Compare And Swap)
  b.乐观锁主要操作变量的     

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.2synchronized
属于悲观锁,当一个线程拿到锁对象之后,其他线程拿不到,执行不了,当一个线程做操作的时候,其他线程操作不了->排队
2.3.Lock和synchronized区别(悲观锁和乐观锁的区别)
  1. Lock属于乐观锁 , 使用多个线程操作的是同一个变量

    synchronized属于悲观锁 , 使用多个线程操作一段代码

  2. 乐观锁 : 线程A在操作变量时 , 允许线程B操作 , 只是会先判断 , 如果有问题 , 就放弃本次操作 , 如果判断没有问题 , 则就正常操作

    悲观锁 : 当线程A正在操作的时候 , 不允许线程B执行 , 要等A出来之后B才有可能进入执行

  3. 相对来说 , 悲观锁效率比较低 , 乐观锁效率比较高

当多线程操作同一个数据时 , 会出现以下问题 :

1.可见性

i=9,变量i的初始值为9,每一个线程的操作都是减1。两个线程AB同时访问变量,B先执行i-1,在将结果i=8同步到内存中,A线程也执行i-1,这时i=9的状态就被执行两次,出现线程安全问题。

线程安全问题产生的原因:一个线程对共享数据的修改不能立即被其他线程所见。

解决:给共享的变量加上关键字:volatile

2.有序性

多行代码的编写顺序和编译顺序。-> 问题原因
有些时候,编译器在编译代码时,为了提高效率,会对代码“重排”:

.java文件
int a = 0;		//第一行
int b = 20;		//第二行
int c = a / b;	//第三行

在执行第三行之前,由于第一行和第二行的先后顺序无所谓,所以编译器可能会对“第一行”和“第二行”进行代码重排:
.class
int b = 20;
int a = 0;
int c = a / b;

但在多线程环境下,这种重排可能是我们不希望发生的,因为:重排,可能会影响另一个线程的结果,所以我们不需要代码进行重排

解决:给共享的变量加上关键字:volatile

3.原子性

指的是一个操作不可中断,即在多线程并发的环境下,一个操作一旦开始,就会在同一个CPU时间片内执行完毕

volatile解决不了原子性问题,所以为了多线程操作同一个数据出现的原子性问题,我们可以使用原子类

Atomicxxx->xxx代表具体数据类型  ->  原子类的实现原理就是乐观锁

线程池

​ 之前来一个线程任务,就需要创建一个线程对象,用完还要销毁线程对象,如果频繁的创建和销毁对象,会耗费内存资源,所以我们就想线程对象能不能循环利用,用的时候从线程池中获取,用完之后归还.

1.创建线程池对象,指定池子中最多能有多少个线程对象,假设22.来了一个线程任务,看池子中有没有线程对象,如果没有,创建线程对象,给线程任务用完之后,还回去
3.来了第二个线程任务,看池子中有没有空闲的线程对象,如果有就拿来用,如果没有,就创建,给线程任务用,用完就还回去.
4.来了第三个线程任务,看池子中有没有空闲的线程对象,如果有就拿来用,如果没有,等待,等之前的线程任务执行完毕了,归还了线程对象,再使用,使用完毕再还回去
1.概述:Executors线程池对象
2.获取:static ExecutorService newFixedThreadPool(int nThreads)
    	a.参数:指定线程池中最多的线程对象数量
        b.返回值:ExecutorService 时管理线程对象的
3.ExecutorService中的方法:
	Future<?> submit(Runnable task) 提交一个Runnable任务用于执行
        
        a.返回值:Future接口
          用于接收run方法的返回值的,但是run方法没有返回值,所以可以不用Future接收了
4.ExecutorService中的方法:
  void shutdown():启动一次顺序关闭,执行以前提交的任务,但不接受新任务。 
public class MyRunnable1 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"...正在执行");
    }
}
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"...执行了");
    }
}
public class Test02 {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(2);
        es.submit(new MyRunnable1());
        //正常是需要返回值的,返回一个Future对象接收,但是run方法没有返回值所以不需要接收
        es.submit(new MyRunnable());
        es.submit(new MyRunnable());
        //shutdown()启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
        es.shutdown();
    }
}

Callable接口

1.概述:Callable是一个接口,类似与Runnable
2.方法:
	V call() -> 设置线程任务的,类似与run方法
3.call方法和run方法的区别:
	相同点:都是设置线程任务的
    不同点:
			call方法有返回值,而且有异常可以throws,因为他的父类抛了throws
            run方法没有返回值,而且有异常不可以throws,因为它的父类没有抛throws
4.Callable可以结合线程池使用:
	Future<T> submit(Callable<T> task)
    返回值:Future接口用于接收call方法的返回值
        
    Future中有一个方法,是将接收过来的结果获取出来: V get()
5.注意:
	a.实现Callable时需要指定泛型<V>
    b.泛型:用于指定我们操作什么类型的数据,<>中只能写引用数据类型,如果泛型不写,默认时Object类型数据
    c.实现Callable接口时,指定的泛型是什么类型的,重写的call方法返回值就是什么类型的
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "胡图图不糊涂";
    }
}
public class Test03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<String> future = es.submit(new MyCallable());
        System.out.println(future.get());
        es.shutdown();
    }
}

Callable练习

需求:创建两个线程任务,一个线程任务完成1-100的和,一个线程任务返回一个字符串
public class MySum implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Integer sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum+=i;
        }
        return sum;
    }
}
public class MyString implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "哇啊哈哈哈哈哈!";
    }
}
public class Test04 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<String> submit = es.submit(new MyString());
        Future<Integer> submit1 = es.submit(new MySum());
        System.out.println(submit.get());
        System.out.println(submit1.get());
        es.shutdown();
    }
}

定时器_Timer

1.概述:定时器
2.构造:
	Timer()
3.方法:
	void schedule(TimerTask task,Date firstTime,long period)
        		task:抽象类,Runnable的实现类
                firstTime:从什么时间开始执行
                period:每隔多长时间执行一次,设置毫秒值
                   
    void cancel():取消定时器任务
public class Test {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("啊哈哈哈");
            }
        },new Date(),1000L);
        //timer.cancel();   取消定时器任务
    }
}

正则表达式

1.正则表达式的概念及演示

1.概述:是一个字符串,用来表示一种规则的字符串
2.作用:校验
3.比如:校验一个QQ号
      不能是0开头
      必须都是数字
      5-154.String中的一个方法:用来校验字符串是否符合正则表达式
  boolean matches(String regex) 
public class Exercise {
    public static void main(String[] args) {
        //匹配qq号
        boolean b = "2462773479".matches("[1-9][0-9]{4,14}");
        System.out.println(b);
    }
}

2.正则表达式-字符类

java.util.regex.Pattern:正则表达式的编译表示形式。
    正则表达式-字符类:[]表示一个区间,范围可以自己定义
        语法示例:
        1. [abc]:代表a或者b,或者c字符中的一个。
        2. [^abc]:代表除a,b,c以外的任何字符。
        3. [a-z]:代表a-z的所有小写字符中的一个。
        4. [A-Z]:代表A-Z的所有大写字符中的一个。
        5. [0-9]:代表0-9之间的某一个数字字符。
        6. [a-zA-Z0-9]:代表a-z或者A-Z或者0-9之间的任意一个字符。
        7. [a-dm-p]:a 到 d 或 m 到 p之间的任意一个字符      

3.正则表达式-逻辑运算符

 正则表达式-逻辑运算符
        语法示例:
        1. &&:并且
        2. | :或者

4.正则表达式-预定义字符

正则表达式-预定义字符
    语法示例:
    1. "." : 匹配任何字符。(重点)  不能加[]
    2. "\\d":任何数字[0-9]的简写;(重点)
    3. "\\D":任何非数字[^0-9]的简写;
    4. "\\s": 空白字符:[ \t\n\x0B\f\r] 的简写
    5. "\\S": 非空白字符:[^\s] 的简写
    6. "\\w":单词字符:[a-zA-Z_0-9]的简写(重点)
    7. "\\W":非单词字符:[^\w]

5.正则表达式-数量词

 正则表达式-数量词
        语法示例:x代表字符
        1. X? : x出现的数量为 0次或12. X* : x出现的数量为 0次到多次 任意次
        3. X+ : x出现的数量为 1次或多次 X>=14. X{n} : x出现的数量为 恰好n次 X=n次
        5. X{n,} : x出现的数量为 至少n次 X>=n次  x{3,}
        6. X{n,m}: x出现的数量为 n到m次(n和m都是包含的)   n=<X<=m

6.正则表达式-分组括号( )

正则表达式-分组括号( )  (abc)

7.String类中和正则表达式相关的方法

 String类中和正则表达式相关的方法
        boolean matches(String regex) 判断字符串是否匹配给定的正则表达式。
        String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串。
        String replaceAll(String regex, String replacement)把满足正则表达式的字符串,替换为新的字符

8.正则表达式生成网址:

https://www.sojson.com/regex/generate
  1. “.” : 匹配任何字符。(重点) 不能加[]
    2. “\d”:任何数字[0-9]的简写;(重点)
    3. “\D”:任何非数字[^0-9]的简写;
    4. “\s”: 空白字符:[ \t\n\x0B\f\r] 的简写
    5. “\S”: 非空白字符:[^\s] 的简写
    6. “\w”:单词字符:[a-zA-Z_0-9]的简写(重点)
    7. “\W”:非单词字符:[^\w]

### 5.正则表达式-数量词

~~~java
 正则表达式-数量词
        语法示例:x代表字符
        1. X? : x出现的数量为 0次或1次
        2. X* : x出现的数量为 0次到多次 任意次
        3. X+ : x出现的数量为 1次或多次 X>=1次
        4. X{n} : x出现的数量为 恰好n次 X=n次
        5. X{n,} : x出现的数量为 至少n次 X>=n次  x{3,}
        6. X{n,m}: x出现的数量为 n到m次(n和m都是包含的)   n=<X<=m

6.正则表达式-分组括号( )

正则表达式-分组括号( )  (abc)

7.String类中和正则表达式相关的方法

 String类中和正则表达式相关的方法
        boolean matches(String regex) 判断字符串是否匹配给定的正则表达式。
        String[] split(String regex) 根据给定正则表达式的匹配拆分此字符串。
        String replaceAll(String regex, String replacement)把满足正则表达式的字符串,替换为新的字符

8.正则表达式生成网址:

https://www.sojson.com/regex/generate
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值