java线程--狂神

线程基本概念

学习视频来自B站,感谢狂神的分享:B站视频地址

进程:操作系统中运行的程序,比如QQ、游戏、IDE;
线程:同一个进程可以包含多个线程
Process与Thread
普通方法调用与多线程调用:
在这里插入图片描述

线程的创建

1. 继承Thread类

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("这里执行具体方法");
    }

    public static void main(String[] args) {
        //创建对象
        MyThread myThread = new MyThread();
        myThread.run();     //和后续代码顺序执行,只有一条主线
        myThread.start();   //启动另一个线程,和后续代码并行执行
    }
}

在这里插入图片描述

2. 实现Runnable接口

//1.实现Runnable接口并重写run方法
//2.将实现Runnable接口的类,放入执行线程,调用start方法
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("这里执行具体方法");
    }

    public static void main(String[] args) {
        //创建Runnable接口实现类的对象
        MyRunnable mr = new MyRunnable();
        //创建线程对象,并通过它开启线程
        new Thread(mr).start();
    }
}

实现Runnable接口
两种方法的对比:
两种方法的对比
并发问题:

//多个线程同时买火车票
public class TickRunnable implements Runnable {
    private int tickNum = 10;	//票数
    @Override
    public void run() {
        while (true){
            if(tickNum <= 0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+" --> 获得第"+tickNum-- +"张票");
        }
    }

    public static void main(String[] args) {
        TickRunnable tickRunnable = new TickRunnable();
        //同一个对象操,被多个线程使用
        //未做并发处理,会出现多个人抢到同一张票的问题
        new Thread(tickRunnable,"张三").start();
        new Thread(tickRunnable,"李四").start();
        new Thread(tickRunnable,"黄牛").start();
    }
}
//模拟龟兔赛跑
public class RaceRunnable implements Runnable {
    private static String winner;   //胜利者
    @Override
    public void run() {
        //一共跑一百步
        for (int i = 0; i <= 100; i++) {
            if(i == 100 && Thread.currentThread().getName().equals("兔子")){
                Thread.sleep(200);  //如果是兔子跑到100步,沉睡200毫秒
            }
            boolean isOver = isOver(i);
            if(isOver){
                break;
            }
        }
    }

    //跑到100,给winner赋值,结束比赛
    private boolean isOver(int step){
        if(winner != null){
            return true;
        }else if(step >= 100){
            winner = Thread.currentThread().getName();
            System.out.println("胜利者是:"+winner);
            return true;
        }else{
            System.out.println(Thread.currentThread().getName()+"--->跑了第"+step+"步");
            return false;
        }
    }

    public static void main(String[] args) {
        RaceRunnable rr = new RaceRunnable();
        new Thread(rr,"兔子").start();
        new Thread(rr,"乌龟").start();
    }
}

3. 实现Callable接口

在这里插入图片描述

public class MyCallable implements Callable<Integer> {
    private static Integer index = 0;   //记录第几个创建
    //实现call方法,并返回值
    @Override
    public Integer call() throws Exception {
        return index++;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable mc1 = new MyCallable();
        MyCallable mc2 = new MyCallable();
        MyCallable mc3 = new MyCallable();
        //创建执行服务
        ExecutorService executor = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Integer> submit1 = executor.submit(mc1);
        Future<Integer> submit2 = executor.submit(mc2);
        Future<Integer> submit3 = executor.submit(mc3);
        //获取结果
        Integer index1 = submit1.get();
        Integer index2 = submit2.get();
        Integer index3 = submit3.get();
        //关闭服务
        executor.shutdownNow();
    }
}

知识扩充

静态代理

//--------------- 代理类与被代理类实现同一个接口 ---------------
//代理类 传入 被代理类的对象
public class StaticProxy {
    public static void main(String[] args) {
        You you = new You();    //真实对象
        WiddingConpany widdingConpany = new WiddingConpany(you);    //代理类对象
        widdingConpany.HappyMarry();    //通过代理类执行方法
    }
}
//定义结婚接口,让实现类和代理类都实现这个接口
interface Marry{
    void HappyMarry();
}
//实现类:真实结婚对象
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("新郎新娘:结婚了,真开心!");
    }
}
//代理类:婚庆公司
class WiddingConpany implements Marry{
    private Marry target;   //指向被代理的真实对象
    public WiddingConpany(Marry target){
        this.target = target;
    }
    @Override
    public void HappyMarry() {
        before();   //调用真实对象方法之前
        target.HappyMarry();
        after();    //调用真实对象方法之后
    }
    private void before(){
        System.out.println("代理类:结婚之前,布置现场");
    }
    private void after(){
        System.out.println("代理类:结婚之后,收尾款");
    }
}
//--------------- Thread类也实现了Runnable接口 ---------------
//线程也是代理模式:Thread类是代理类,代理了Runnable接口的实现类(匿名内部类)
 new Thread(new Runnable() {
     @Override
     public void run() {
         System.out.println("实现Runnable接口");
     }
 }).start();
 //等同于下面的方式:
 new Thread(()->{
            System.out.println("函数式编程,实现Runnable接口");
        }).start();

lambda表达式

函数式接口: 只包含唯一一个抽象方法的接口(只有一个方法,且抽象);函数式接口,可通过lambda表达式创建该接口的对象。

//Runnable就是函数式接口:
public interface Runnable{
	public abstract void run();
}
//推导过程如下:
public class LambdaInfer {
    //2. 通过静态内部类
    static class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("2.通过静态内部类实现:lambda --》2");
        }
    }
    public static void main(String[] args) {
        //3.通过局部内部类实现
        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("3.通过局部内部类实现:lambda --》3");
            }
        }
        //---------------------- 推导过程: ----------------------
        //1.实现类
        ILike like = new Like1();
        like.lambda();
        //2.静态内部类
        like = new Like2();
        like.lambda();
        //3.局部内部类
        like = new Like3();
        like.lambda();
        //4.通过匿名内部类实现
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("4.通过匿名内部类:lambda --》4");
            }
        };
        like.lambda();
        //5.用lambda简化
        like = ()->{
            System.out.println("5.通过lambda简化:lambda --》5");
        };
        like.lambda();
    }
}
输出结果如下:
1.通过实现类实现:lambda --1
2.通过静态内部类实现:lambda --2
3.通过局部内部类实现:lambda --3
4.通过匿名内部类实现:lambda --4
5.通过lambda简化:lambda --5

线程的状态

线程的状态:创建、就绪、运行、阻塞、死亡
线程状态之间转换

1.停止

1.不建议使用JDK提供的 stop()、destory()方法(已废弃)
2.建议使用 标志位 ,终止线程的运行。

public class RunnableStop implements Runnable {
    private boolean flag = true;
    @Override
    public void run() {
        int index = 0;
        //通过标志位,执行线程方法体
        while (flag){
            System.out.println("run -->"+index++);
        }
    }
    //公共方法,转换标志位
    public void stop(){
        flag = false;
    }

    public static void main(String[] args) {
        RunnableStop runnableStop = new RunnableStop();
        new Thread(runnableStop).start();

        for (int i = 0; i < 10000; i++) {
            System.out.println("main: "+i);
            if(i == 900){
                runnableStop.stop();    //转换线程标志位,停止方法
                System.out.println("线程停止!!");
            }
        }
    }
}

2.休眠 sleep

在这里插入图片描述
作用:

  1. 模拟网络延时,放大问题发生的可能性(抢票问题)
  2. 模拟系统倒计时

3. 礼让 yield

在这里插入图片描述

public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }
}

class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+ "--线程 start");
        Thread.yield();     //调用yield方法,当前线程重新参与cpu竞争
        System.out.println(Thread.currentThread().getName()+ "--线程 end");
    }
}

4. 强制执行 join

Join 合并线程,等待此线程执行完成后,再执行其他线程。

public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("run线程 --》"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new TestJoin());
        thread.start();

        for (int j = 0; j < 300; j++) {
            System.out.println("主线程 -- "+j);
            //当j为200的时候,调用join方法使主线程阻塞,将thread执行完
            if(j == 200){  thread.join(); }
        }
    }
}

5.状态监测 Thread.State

java.lang.Thread.State 继承自 java.lang.Enum

  • NEW :尚未启动的线程,处于此状态
  • RUNNABLE:在java虚拟机中执行的线程,处于此状态
  • BLOCKED:被阻塞等待监视器锁定的线程,处于此状态
  • WAITING:在等待另一个线程执行特定动作的线程,处于此状态
  • TIMED_WAITING:在等待另一个线程执行动作达到指定等待时间的线程,处于此状态
  • TERMINATED:已退出的线程,处于此状态
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread( ()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000); //睡眠1秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("--------- 线程已结束 ---------");
        });
        //--------- 获取线程状态,并输出:---------
        Thread.State state = thread.getState();
        System.out.println(state);//1.未执行,输出NEW
        //2.启动线程
        thread.start();
        state = thread.getState();
        System.out.println(state);  //输出  RUNNABLE
        //只要线程不终止,一直输出状态
        while(state != Thread.State.TERMINATED){
            //3.线程阻塞
            Thread.sleep(100);
            state = thread.getState();
            //输出  TIMED_WAITING;线程执行完之后,输出  TERMINATED
            System.out.println(state);  
        }
    }
}

6.线程优先级

线程优先级

public class MyPriority implements Runnable {
    public static void main(String[] args) {
        MyPriority priority = new MyPriority();
        //线程优先级:先设置后启动[默认为5。范围1 至10,超出报异常]
        Thread t1 = new Thread(priority, "a");
        Thread t2 = new Thread(priority, "b");
        Thread t3 = new Thread(priority, "c");
        //默认权重为5
        t1.start();
        //设置最高,先设置再启动
        t2.setPriority(10);
        t2.start();
        //设置最低
        t3.setPriority(1);
        t3.start();

    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" 优先级为:"+Thread.currentThread().getPriority());
    }
}

7/守护线程

线程分为用户线程守护线程
虚拟机确保 用户线程 执行完,但不用等 守护线程 执行完

public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        //创建上帝,设置为守护线程。虽然未设置结束条件,
        // 但在用户线程结束后,仍会结束
        Thread godThread = new Thread(god);
        godThread.setDaemon(true);  //默认为false
        godThread.start();
        //创建用户线程,虚拟机保证用户线程执行结束
        new Thread(you).start();

    }

}

class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("--上帝守护--");
        }
    }
}

class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("在人世间第 "+i+" 天");
        }
        System.out.println("goodbye world!");
    }
}

线程同步

并发

并发:同一个对象多个线程 同时操作

并发同一个对象多个线程 同时操作
线程同步线程同步是一种等待机制,多个同时访问此对象的线程,
进入这个对象的等待池形成队列。
前面的线程使用完,下一个线程再使用
同步的条件队列+锁

线程同步
不安全的线程:

//不安全的买票线程:
// 三个人同时得到最后一张,输出 -1
public class UnsafeByTicket {

    public static void main(String[] args) {
        ByTicket byTicket = new ByTicket();
        
        new Thread(byTicket,"aa").start();
        new Thread(byTicket,"bb").start();
        new Thread(byTicket,"cc").start();
    }

}
class ByTicket implements Runnable{
    private static int ticketNum = 10;
    private boolean flag = true;
    @Override
    public void run() {
        while (flag){
            buy();
        }
    }
	//定义买票方法
    public void buy() throws InterruptedException {
        if(ticketNum <= 0){		//票数为0的时候,停止线程执行
            flag = false;
            return;
        }
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName()+"买了--->"+ticketNum --);
    }
}
输出结果:
bb买了--->1
cc买了--->0
aa买了--->-1
// ArrayList 线程不安全,被多个线程同时操作,可能出现混乱
//扩充:CopyOnWriteArrayList 线程安全,不用添加同步块
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> strList = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {   //添加10000条数据
            new Thread(()->{
                strList.add(Thread.currentThread().getName());
            }).start();
        }
        //沉睡10秒,模拟延迟
        Thread.sleep(10000);
        //输出结果,很可能不到10000(多个线程同时添加一个位置)
        System.out.println(strList.size());
    }
}

同步方法与同步块

同步方法
在这里插入图片描述

  • 同步方法,synchronized锁定的是当前对象。如果线程中的增删改操作的不是该对象,则起不到方法同步的效果
  • synchronized方法快,可以锁定任何对象,但是建议将变化的对象作为同步监视器
    买票:同步方法修改
    同步块示例

死锁

多个线程各自占有一些公共资源,并且互相等待其他线程占有的资源才能运行,导致线程都停止执行。某一个同步块同事拥有两个以上对象的锁,就可能造成 “死锁

  • 死锁产生的四个必要条件
    1.互斥条件:一个资源每次只被一个线程使用
    2.请求与保持:一个线程因请求自愿而阻塞时,对已获得的资源保持不放
    3.不剥夺条件:进程已获得的资源,在未使用完之前,不可强行剥夺
    4.循环等待:多个线程之间,形成一种头尾相接的循环等待资源关系
//灰姑娘先拿到口红,白雪公主先拿到镜子;两人相互等待对方的资源
public class DeadLock {
    public static void main(String[] args) {
        MakeUp girl1 = new MakeUp(1, "灰姑娘");
        MakeUp girl2 = new MakeUp(2, "白雪公主");

        girl1.start();
        girl2.start();
    }
}
//口红
class Lipstick{}
//镜子
class Mirror{}

class MakeUp extends Thread{
    //用static保证资源唯一
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    //实体类属性
    int choice;     //选择:1先用口红,2先用镜子
    String girlName;   //名字

    public MakeUp(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        try {
            girlMakeUp();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //定义化妆方法
    public void girlMakeUp() throws InterruptedException {
        if(choice == 1){
        	//synchronized嵌套使用,导致相互等待,造成死锁
            synchronized (lipstick){
                System.out.println(girlName+"-->拿到了口红");
                Thread.sleep(1000); //拿到口红,使用1秒
                synchronized (mirror){
                    System.out.println(girlName+"-->拿到了镜子");
                }
            }
        }else{
            synchronized (mirror){
                System.out.println(girlName+"-->拿到了镜子");
                Thread.sleep(2000); //拿到镜子,使用2秒
                synchronized (lipstick){
                    System.out.println(girlName+"-->拿到了口红");
                }
            }
        }
    }
}
//程序卡死,输出结果:
//白雪公主-->拿到了镜子
//灰姑娘-->拿到了口红
解决方法:synchronized不要嵌套使用

lock(锁)

在这里插入图片描述

public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();

        new Thread(testLock2,"aa").start();
        new Thread(testLock2,"bb").start();
        new Thread(testLock2,"cc").start();
    }
}
//使用ReentrantLock实现线程同步
class TestLock2 implements Runnable{
    private static int ticketNum = 10; //一共10张票
    private ReentrantLock lock = new ReentrantLock();   //Lock的实现类
    @Override
    public void run() {
        try {
            lock.lock();  // ------ 对下面代码块添加 锁---
            while (ticketNum > 0){
                Thread.sleep(1000);
                System.out.println("买了票:-->"+ticketNum-- );
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  // ------ 解 锁------
        }
    }
}

lock与synchronized对比

线程协作

java提供了几种方法,解决线程之间的通讯:

方法名作用
wait()表示线程一直等待,直到其他线程通知。
与synchronized不同,会释放锁
wait(long timeout)指定等待的毫秒数
notify()唤醒一个处于等待状态的线程
notifyAll()唤醒同一个对象上所有调用wait()方法的线程,
优先级高的线程优先调度

注:以上方法都是Object类的方法,都只能在同步方法或者同步代码块中调用,否则将抛出 IllegalMonitorStateException

管程法

管程法

//使用管程法,利用缓冲区测试生产者、消费者
public class TestPC {
    public static void main(String[] args) {
        //定义缓冲区
        SynContainer synContainer = new SynContainer();
        //启动生产者、消费者
        new Productor(synContainer).start();
        new Consumer(synContainer).start();
    }
}

// ------------- 生产者 -------------
class Productor extends Thread{
    //加入缓冲区并创建构造方法
    private SynContainer synContainer;
    public Productor(SynContainer synContainer) {
        this.synContainer = synContainer;
    }
    //开始生产产品
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            synContainer.push(new Chicken(i));
//            System.out.println("生产了-->"+i);
        }
    }
}

// ------------- 消费者 -------------
class Consumer extends Thread{
    //加入缓冲区并创建构造方法
    private SynContainer synContainer;
    public Consumer(SynContainer synContainer) {
        this.synContainer = synContainer;
    }
    //开始消费产品
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            Chicken chicken = synContainer.pop();
//            System.out.println("消费了--"+chicken.id);
        }
    }
}

// ------------- 产品 -------------
class Chicken{
    int id; //产品编号
    public Chicken(int id) { this.id = id;}
}

// ------------- 缓冲区 -------------
class SynContainer{
    Chicken[] chickens = new Chicken[10];   //缓冲区可放10只鸡
    int index = 0;  //缓冲区的下标

    // --- 生产者放入产品
    public synchronized void push(Chicken chicken){
        //判断缓冲区是否满了:如果是则等待,并通知其他线程消费
        if(index == chickens.length){
            //等待消费者
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //没有满,则继续放入产品
        System.out.println("生产了-->"+chicken.id);
        chickens[index] = chicken;
        index ++;
        //唤醒其他线程
        this.notifyAll();
    }
    
    // --- 消费者消费产品 --- 
    public synchronized Chicken pop(){
        //判断缓冲区是否满了:如果是则等待,并通知其他线程消费
        if(index == 0){
            //等待并唤醒其他线程
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有产品,可以消费
        index --;   //不可将这一行放到下面,否则下标越界
        Chicken chicken = chickens[index];
        System.out.println("消费了--"+chicken.id);
        //唤醒其他线程,并返回结果
        this.notifyAll();
        return chicken;
    }

}

信号灯法

public class TestPC2 {
    public static void main(String[] args) {
        Tv tv = new Tv();

        new Player(tv).start();
        new Watcher(tv).start();
    }
}
// ------------- 演员 ------------- 
class Player extends Thread{
    private Tv tv;
    public Player(Tv tv) { this.tv = tv; }
    //开始表演节目
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i%2 == 0){
                tv.play("亮剑--李云龙");
            }else{
                tv.play("抖音--记录美好生活");
            }
        }
    }
}

// ------------- 观众 ------------- 
class Watcher extends Thread{
    private Tv tv;
    public Watcher(Tv tv) { this.tv = tv; }
    
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            tv.watch();
        }
    }
}

// ------------- 节目 ------------- 
class Tv{
    String tvName;  //节目名称
    boolean flag = true;   //信号灯:演员表演为true; 观众观看为false

    //表演节目
    public synchronized void play(String tvName){
        this.tvName = tvName;
        //如果标识位为true,演员开始表演
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("表演-------->"+this.tvName);
        this.flag = !this.flag;
        this.notifyAll();

    }
    //观看节目
    public synchronized String watch(){
        //标识位为false,观众开始观看
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了:"+this.tvName);
        this.flag = !this.flag;
        this.notifyAll();
        return this.tvName;
    }
}

注:要在等待(wait)与唤醒(notifyAll)之间处理业务,否则会出现不可预测结果

线程池

背景:经常创建和销毁使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入 线程池 中,使用时直接获取,使用完放回池子中。避免频繁的创建销毁,可以重复利用
好处:

  • 提高响应速度(减少了创建线程的时间)
  • 降低资源消耗(重复利用线程池中的线程,不需要重复创建)
  • 便于线程管理:
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时,最多保持多长时间后会终止
    在这里插入图片描述
public class TestPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1.创建服务,创建线程池
        ExecutorService service = Executors.newFixedThreadPool(9);
        
        //-----2.execute方法,没有返回值-----
        service.execute(new MyRunnable2());
        service.execute(new MyRunnable2());
        
        //-----3.submit方法,可以获取返回值-----
        Future<String> future = service.submit(new Callable2());
        System.out.println(future.get());
        //4.关闭连接
        service.shutdownNow();
    }
}

class MyRunnable2 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

class Callable2 implements Callable<String>{
    @Override
    public String call() throws Exception {
        return "你好:"+Thread.currentThread().getName();
    }
}

Callable接口的实现类,也可通过下面的方式,获取返回值:

//另一种写法:
public class NewThread {
    public static void main(String[] args) {
        new MyThread1().start();    //第一种启动方式
        new Thread(new MyThread2()).start();    //第二种启动方式
        
        //第三种启动方式
        FutureTask<String> futureTask = new FutureTask<String>(new MyThread3());
        new Thread(futureTask).start();     
        try {
            String str = futureTask.get();
            System.out.println(str);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

// --- 继承Thread类 ---
class MyThread1 extends Thread{
    @Override
    public void run() { System.out.println("继承Thread类"); }
}

// --- 实现Runnable接口,无返回值 ---
class MyThread2 implements Runnable{
    @Override
    public void run() { System.out.println("实现Runnable接口"); }
}
// --- 实现Callable,有返回值 ---
class MyThread3 implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("实现Callable接口");
        return "Callable接口实现类";
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值