Java多线程学习以及使用

一,什么是多线程在这里插入图片描述

一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

二,创建线程的三种方式

1,通过继承 Thread 类本身;

一个类如果继承Thread类后,它就拥有了Thread类的所有方法

//创建方式1:继承线程类Thread
//使用步骤1.继承Thread,
public class Create01 extends Thread{
   @Override
   //2.重写run()方法
   public void run() {
       for (int i = 0; i <100 ; i++) {
           System.out.println(Thread.currentThread().getName()+i);
       }
   }
   public static void main(String[] args) {
       //3.创建该类的对象开启线程start();
       Create01 c = new Create01();
       c.setName("haha");//设置该线程的名称
       c.start();//开启线程
       for (int i = 0; i <100 ; i++) {
       //主线程输出
       //Thread.currentThread().getName()得到当前线程的名字
           System.out.println(Thread.currentThread().getName()+i);
       }
   }
}

执行效果图:
在这里插入图片描述
创建的线程和主线程交替输出,表示交替执行,主线程开启其他线程后,线程执行顺序主要由CPU调度

2,通过实现 Runnable 接口;

package study01;
//创建方式二:实现Runnable接口
//步骤1:实现Runnable接口
public class Create02 implements Runnable{
    //步骤2.实现run()方法
    public void run() {
        for (int i = 0; i <100 ; i++)
        System.out.println(Thread.currentThread().getName()+i);
    }

    public static void main(String[] args) {
        //步骤三,通过该类的对象创建Thread类对象开启线程(静态代理)
        //静态代理:即通过引用/对象传递(一般用接口进行约束)借助代理类的对象,对该引用/对象做某些使用和修改
        //例如别人用你的身份证去上网,帮你开了会员还充了网费
        new Thread(new Create02(),"小李子").start();
        /*完整步骤:
        1.
        Thread t1 = new Thread(new Create02());
        t1.setName("线程名字");
        t1.start();
        2.
        Thread t1 = new Thread(new Create02(),"线程名字");
        t1.start();
        */
        for (int i = 0; i <100 ; i++)
            System.out.println(Thread.currentThread().getName()+i);
    }
}

3,通过 Callable 和 Future 创建线程。

Callable接口优点

  • 1.Callable接口要重写的call()方法是可以有返回值的。
  • 2.call()方法可以抛出异常,供外部捕获。
  • 3.Callable接口支持泛型。
import util.DownloadUtil;
import java.util.concurrent.*;
//使用步骤1:实现Callable接口
public class Create03 implements Callable<String> {
    private String url;
    private String fileName;
    public Create03(String url, String fileName) {
        //通过构造器给线程传递参数
        this.url = url;
        this.fileName = fileName;
    }

    //步骤2:实现call方法,类似于run(),但可以有返回值,能抛出异常
    public String call(){
        DownloadUtil.download(this.url,this.fileName);//自己写的一个网络文件下载方法
        return "成功下载了"+fileName;
    }

    public static void main(String[] args) {
        //步骤3:创建该类对象
        Create03 c1 = new Create03("https://www.aa.png","1.png");
        Create03 c2 = new Create03("https://v/index10.ts","2.ts");
        //步骤4:创建执行服务/线程池,后面会说
        ExecutorService ser = Executors.newFixedThreadPool(2);//池子容量
        //步骤5:提交执行
        Future<String> result1 = ser.submit(c1);
        Future<String> result2 = ser.submit(c2);
        //步骤6,:获取结果
        try {
            String msg1 = result1.get();
            String msg2 = result2.get();
            System.out.println(msg1);
            System.out.println(msg2);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

4.龟兔赛跑案例

package study01;

public class Race implements Runnable{
    private static String winner;//静态的,输入该类,而不属于该类的任何对象
    public void run() {
        for (int i = 0; i <=100; i++) {
            if (i%20==0&&"兔子".equals(Thread.currentThread().getName())) {
                try {
                    System.out.println("兔子睡觉了");
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (isWin(i)){
                System.out.println("胜利者"+winner);
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }

    }
    public Boolean isWin(int steps){
        if (winner!=null){
            return true;
        }else if (steps>=100){
            winner=Thread.currentThread().getName();
            return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Race race =new Race();
        Thread a = new Thread(race,"兔子");
        Thread b = new Thread(race,"乌龟");
        a.start();
        b.start();
        System.out.println(winner+"666666");
    }
}

三,线程方法

线程类的常用方法:

终止线程:建议使用信号灯/设置标志位进行终止,不建议使用stop()和destroy()方法

返回值方法作用
static voidsleep(long millis)线程休眠指定时间,单位毫秒,不会让当前线程释放它所持有的锁。
StringtoString()返回此线程的字符串表示形式,包括线程的名称、优先级和线程组。
static voidyield()线程礼让,停止当前线程的运行状态,使线程从运行状态回到就绪状态等待CPU调用,不一定礼让成功,主要看CPU心情
voidstart()导致该线程开始执行;java虚拟机调用这个线程的 run方法。
voidstop()(方法已经过时)停止该线程
voiddestroy()(方法已经过时)摧毁该线程
intgetPriority()返回此线程的优先级
Thread.StategetState()返回此线程的状态。
voidinterrupt()中断这个线程。
booleanisDaemon()判断该线程是守护线程。
voidjoin()合并所有线程,让调用该线程的对象优先执行,知道该线程死亡
voidjoin(long millis)给该线程millis毫秒的时间执行(相当于join()方法加了限时条件)。
voidsetName(String name)改变该线程的名称等于参数 name。
voidsetPriority(int newPriority)更改此线程的优先级。
booleanisAlive()测试这个线程是否还活着。
StringgetName()返回此线程的名称。
longgetId()返回此线程的名称。
ThreadGroupgetThreadGroup()返回此线程所属的线程组。
voidsetDaemon(boolean on)true为设置该线程为守护线程

四,线程状态

1.新建状态:

使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

2.就绪状态:

当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

3.运行状态:

如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

4.阻塞状态:

如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

5.等待阻塞:

运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

6.同步阻塞:

线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

7.其他阻塞:

通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

8.死亡状态:

一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程状态

9.Thread.State

Thread.State是Thread类的内部枚举类,用于表示线程运行的状态
通过调用getState()方法得到线程当前的的运行状态
它的常用枚举量有
1.NEW
新建状态:即单纯地创建一个线程。
2.RUNNABLE运行状态:当线程获得CPU时间,线程才从就绪状态进入到运行状态。
3.BLOCKED堵塞状态,线程被堵塞
4.WAITING 等待:线程真在等待另一个线程执行特定动作
5.TIMED_WAITING 限时等待:线程正在等待指定的的时间,sleep()
6.TERMINATED终止/退出:线程已经退出执行,

10,线程的生命周期

在这里插入图片描述

五,线程优先级和守护线程

1.线程优先

优先级:priority

Thread.MIN_PRIORITY==1//最低优先级
Thread.NORM_PRIORITY==5//一般优先级(默认)
Thread.MAX_PRIORITY==10//最高优先级

使用setPriority(int newPriority)方法设置线程的优先级
newPriority范围1~10
注意:
优先级建议设置在线程开启,start()之前优先级高,只代表CPU调用该线程的概率高,最高的不一定最先调用

2.守护线程

守护线程Daemon
开启方式:使用方法setDaemon(true)
特点:开启后一保持存活,虚拟机不需要等待守护线程执行完成,用户线程执行完毕后守护线程会自动结束
例子,gc线程
作用:后台操作记录日志,,监控内存,垃圾回收等

六:线程同步:

1:synchronized锁机制

synchronized锁机制分为synchronized方法synchronized块

1.synchronized方法:

使用synchronized关键字使线程同步,每个对象都会有一把锁,synchronized的方法只有获得调用该方法的对象的锁的线程才能执行该方法
1.使用:在一般的方法前加synchronized关键字
注意:synchronized默认锁住的是this,即方法所属类的对象,synchronized关键字和static关键字同时存在于方法签名上,则synchronized锁住的是这个类
2.优点:实现同步
3.缺点:造成性能降低,性能倒置的问题
只有会修改数据的方法才需要用synchronized,避免资源浪费影响效率

2.synchronized块:

1.使用

synchronized(Object obj){//需要锁住的对象
//使用该对象执行数据修改的代码
}

2.锁的对象通常是会发生变化的量,避免资源浪费影响效率
3.优点:实现同步
4.缺点:造成性能降低,性能倒置的问题

3.死锁

当一个线程需要同时获得两个对象的锁时,可能会发生死锁现象
案例:

package study03;

public class DeadLock implements Runnable{

   static final A a = new A();
   static final B b = new B();
   int choose;
   public DeadLock(int choose){
       this.choose = choose;
   }
    @Override
    public void run() {
       if (choose==1){
           synchronized (a){
               System.out.println(Thread.currentThread().getName()+"获得了A对象的锁");
               try {
                   Thread.sleep(1000);//休息一会,扩大不安全事件的发生性
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               synchronized(b){//获得a对象锁的同时,还需要获得b对象的锁
                   System.out.println(Thread.currentThread().getName()+"获得了B对象的锁");
               }
           }
       }else{
           synchronized (b){
               System.out.println(Thread.currentThread().getName()+"获得了B对象的锁");
               try {
                   Thread.sleep(1000);//休息一会,扩大不安全事件的发生性
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               synchronized(a){//获得b对象锁的同时,还需要获得a对象的锁
                   System.out.println(Thread.currentThread().getName()+"获得了A对象的锁");
               }
           }
       }

    }
    public static void main(String[] args) {
        new Thread(new DeadLock(1),"小明").start();
        new Thread(new DeadLock(0),"小红").start();
    }
}
class A{

}
class B{

}

解决办法:将run()方法修改为

  public void run() {
     if (choose==1){
         synchronized (a){
             System.out.println(Thread.currentThread().getName()+"获得了A对象的锁");
             try {
                 Thread.sleep(1000);//休息一会,扩大不安全事件的发生性
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             }
             synchronized(b){//使用a对象锁后退回,再等待获得b对象的锁
                 System.out.println(Thread.currentThread().getName()+"获得了B对象的锁");
             }
   
     }else{
         synchronized (b){
             System.out.println(Thread.currentThread().getName()+"获得了B对象的锁");
             try {
                 Thread.sleep(1000);//休息一会,扩大不安全事件的发生性
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             }
             synchronized(a){//使用b对象锁后退回,再等待获得a对象的锁
                 System.out.println(Thread.currentThread().getName()+"获得了A对象的锁");
             }
         
     }

  }

2.lock可重复锁

lock锁:每次只能有一个线程能对Lock对象加锁,显式的锁的。线程开始访问共享资源前需要先获得Lock对象,达到独占资源的目的。
注意:锁住的是Lock对象

1.锁的创建

1.简单的使用案例

	//一般会使用final关键字
    private final ReentrantLock lock = new ReentrantLock();
    public void a() {
        try{
            lock.lock();//上锁
            //此处为共享资源
            
        }finally {
            lock.unlock();//解锁
            //如果同步块会有异常,建议将unlock()方法写到finally里
        }

lock.lock()lock.unlock()方法之间的部分不属于同步代码块,也不属于同步方法中,不能使用wait()和notify()方法

2.Lock锁和synchronized的区别

  • 1.Lock 的锁定是通过代码实现的,而 synchronized 是在 JVM 层面上实现的。
  • 2.lock锁特点:性能更好,JVM花费更少的时间来调度线程,拥有更好的扩展性(能有很多子类)
  • 3.lock只有代码块锁,synchronized有方法锁和代码块锁
  • 4.lock是显式的锁需要手动关闭,synchronized是隐式的锁出了作用域自动释放
  • 5.优先使用顺序:Lock>同步代码块(已经进入了方法体,分配了相应的资源)>同步方法(在方法体之外)

3.线程通信

  • synchronized只能实现资源同步,不能实现线程通信
  • wait():方法表示线程等待,直到其他线程通知,与sleep()不同,会释放锁
  • wait(long timeout): 等待多少毫秒
  • notify():唤醒一个处于等待的线程
  • notifyAll():唤醒同一个对象上所有调用wait()方法进入等待的线程,优先级高的线程优先调度
  • 这四个方法均属于Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出非法监视器状态异常IllegalMonitorStateException

4.线程同步三大经典案例

1.银行取钱
package study03;
public class WithdrawMoney implements Runnable{
    Account account;//账户
    int withOut;//取出的钱
    public void withOutMoney(int withOut){
        Account.balance = Account.balance-withOut;
    }
    public WithdrawMoney(Account account,int withOut){
        this.account=account;
        this.withOut=withOut;
    }
    public void run(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (account){//锁住账户对象,每次只能有一个线程进入代码块获取资源
        if (Account.balance-this.withOut>0){
                this.withOutMoney(this.withOut);
        }else {
            System.out.println(Thread.currentThread().getName()+"取钱"+this.withOut+",余额不足,取不到");
            return;
        }}
        System.out.println(Thread.currentThread().getName()+"取出"+this.withOut);
        System.out.println("当前账户余额为"+Account.balance);
    }
    public static void main(String[] args) {
        Account account = new Account(1000);//创建一个拥有1000元的账户
        new Thread(new WithdrawMoney(account,500),"小明").start();
        new Thread(new WithdrawMoney(account,1000),"小红").start();
    }
}
class Account{//账户类
    static int balance;//余额
    public Account(int balance){
        Account.balance=balance;
    }
}


2.购买车票
package study03;

public class BuyTicket implements Runnable{
    static int ticket = 10;
    public synchronized boolean hasTicket(){//使用synchronized关键字,只有获得实例对象的锁的线程才能访问,同步方法,
        if (ticket>0){
            System.out.println(Thread.currentThread().getName()+"得到了第"+ticket--+"张票");
            return true;
        }else {
            return false;
        }
    }
    public  void run(){//不建议直接锁住run方法,这样只有一个对象时可能会导致多线程变为单线程
        while(this.hasTicket()){
            try {
                Thread.sleep(1000);//模拟延时,扩大不安全性的发生
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        BuyTicket a = new BuyTicket();//一份资源,三份并发
        new Thread(a,"小明").start();
        new Thread(a,"我").start();
        new Thread(a,"黄牛").start();
    }
}

3.ArrayList集合

ArrayList再高并发中是不安全的

使用Lock锁,实现同步

package study03;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

public class Aggregate {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
		ReentrantLock lock = new ReentrantLock();//创建一个Lock锁,同时只能有一个线程能获得该锁的对象
           for (int i = 0; i <100 ; i++) {
           new Thread(()->{
           try{
          		 lock.lock();//获取Lock对象的锁,只有获得该锁的对象的线程才能继续访问资源
                 list.add(Thread.currentThread().getName());
           }finally{
           		lock.unlock();//释放锁
           }
           }).start();
           }
        try {
            Thread.sleep(5000);
            //remove(int index)移除指定下标的元素并返回被移除的元素
           System.out.println(list.get(list.size()-1));
            //获取集合中最后一个元素
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}


4.安全的集合

CopyOnWriteArrayList类

package study03;

import java.util.concurrent.CopyOnWriteArrayList;

public class SafeAggregate {
//CopyOnWriteArrayList是线程安全的集合,该类不需要我们再去实现线程同步
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i <10000 ; i++) {
            new Thread(()->{
                    list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(5000);
            System.out.println(list.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

七.生产者与消费者模型

1.管程法

package study04;

public class TestPC {
    //生产者与消费者模型,管程法
    public static void main(String[] args) {
        Container container = new Container();//一份资源,开启四条线程
        new Thread(new Producer(container),"小明").start();
        new Thread(new Producer(container),"小明助手").start();
        new Thread(new Consumer(container),"小红").start();
        new Thread(new Consumer(container),"小红助手").start();
        try {
            Thread.sleep(3000);//等待所有线程执行完成
            System.out.println("生产了"+Container.pTimes);
            System.out.println("消费了"+Container.cTimes);
            System.out.println("剩余产品数量"+container.getCount());
            System.out.println("消费者一共等待"+Container.cWait+"次,生产者一共等待"+Container.pWait+"次");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
//生产者
class Producer implements Runnable{
    Container container;
    public Producer(Container container){
        this.container = container;
    }
    //生产者只负责生产产品
    public void run() {
        for (int i = 0; i <100 ; i++) {
            container.push(new Product(Thread.currentThread().getName()+"生产的"+i));//生产
            System.out.println(Thread.currentThread().getName()+"生产了一个"+i+"号产品,剩余"+container.getCount()+"个产品");
        }
    }
}
//消费者
class Consumer implements Runnable{
    Container container;
    public Consumer(Container container){
        this.container = container;
    }
    //消费者只消费产品
    public void run() {
        for (int i = 0; i <100 ; i++) {//消费者光顾次数
            String a =  container.pop().getId();
            System.out.println( Thread.currentThread().getName()+"取出一个"+a+"号产品,剩余"+container.getCount()+"个产品");
        }
    }
}
//产品
class Product{
    private String id;
    public Product(String id) {
        this.id = id;
    }
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
}
//容器
class Container{
    static int pTimes = 0;//成功生产次数
    static int cTimes = 0;//成功消费次数
    static int pWait = 0;//生产者等待次数
    static int cWait = 0;//消费者等待次数
    private Product[] products = new Product[10];//设置容量为10;
    private Product product;//取出的产品
    private int count = 0;//产品数量
    //放入产品
    public synchronized void push(Product product){
        if (count>=products.length){
            //容器满了,停止生产
            try {
                this.wait();//此时会交出对象的锁,等待通知后苏醒,且再次得到锁后继续往下走
                pWait++;//生产者等待一次,此次生产失败
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {//如果不要else就只能实现两条线程的同步,多加线程会出问题,或者如果不加else,也可以把notifyAll()改成notify()
            //容器有空位
            products[count] = product;
            count++;
            pTimes++;
            //生产了产品通知消费者消费
            this.notifyAll();
        }
    }
    //取出产品
    public synchronized Product pop(){
        if (count<=0){
            //没有产品,等待生产者生产
            try {
                this.wait();//消费者等待
                cWait++;//消费者等待一次消费者消费失败,
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {//如果不要else就只能实现两条线程的同步,多加线程会出问题,或者如果不加else,也可以把notifyAll()改成notify()
            //如果有产品
            count--;
            product = products[count];
            cTimes++;
            //消费一个产品,通知生产这生产
            this.notifyAll();
        }

        return product;
    }
    public int getCount(){
        return count;
    }
}

2.信号灯法

package study04;
//生产者与消费者模型之信号灯法
public class TestPC02 {
    public static void main(String[] args) {
        TV tv = new TV();//容器
        new Thread(new Performer(tv)).start();
        new Thread(new Audience(tv)).start();
    }
}
//消费者/观众
class Audience implements Runnable{
    TV tv;
    public Audience(TV tv){
        this.tv=tv;
    }
    public void run(){
    //消费者消费
        for (int i = 0; i < 100; i++) {
            tv.consume();
        }
    }
}
//生产者/演员
class Performer implements Runnable{
    TV tv;
    public Performer(TV tv){
        this.tv=tv;
    }
    public void run(){
    //生产者生产
        for (int i = 0; i < 100; i++) {
            if (i%2==0){
                tv.produce(new Program("斗罗大陆"));
            }else {
                tv.produce(new Program("西行纪"));
            }
        }
    }

}
//产品/节目
class Program{
    String name;
    public Program(String name){
        this.name = name;
    }
}
//容器/TV
class TV{
    Program program ;
    boolean flag = true;
    //true 消费者等待,生产者生产
    //false 生产者等待 ,消费者消费
    public synchronized void produce(Program program){
        if (flag==false){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.program = program;
        System.out.println("生产者生产了"+this.program.name);
        this.flag = !this.flag;
        this.notifyAll();//通知消费者消费
    }
    public synchronized Program consume(){
        if (flag==true){
            try {
                this.wait();//生产者等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.flag = !this.flag;//消费者消费完毕
        this.notifyAll();
        System.out.println("消费者消费了"+this.program.name);
        return this.program;
    }
}

八,线程池

1.线程池的创建

创建方式一:
通过工具类Executors

package study05;

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

//线程池,创建方式1:
public class ThreadPool {
    public static void main(String[] args) {
        //创建一个线程次,容量为3,核心线程数3,最大线程数3,等待队列最多2147483647(默认)
        //最大可容纳线程数=最大线程数+等待线程数
        ExecutorService service = Executors.newFixedThreadPool(3);
        //执行
        for (int i = 0; i <100 ; i++) {
            service.execute(()-> System.out.println(Thread.currentThread().getName()));
        }
        //关闭连接
        service.shutdown();
    }
}

#创建方式二:

package study05;

import java.util.concurrent.*;

public class ThreadPool02 {
    public static void main(String[] args) {
        //核心线程数,最大线程数,存活时间,时间单位,等待队列(队列长度),线程工厂,拒绝策略
        //完整写法:
        //ExecutorService service = new ThreadPoolExecutor(3,5,1L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        //核心部分:
        ExecutorService service = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
        for (int i = 0; i < 8; i++) {
            service.execute(()-> System.out.println(Thread.currentThread().getName()));
        }
        service.shutdown();
    }
}

九,扩展内容–Lambda表达式

关于Lambda的使用有兴趣的读者可以点击下面的链接阅读我的另一篇文章:
java Lambda的使用

创作不易,对您有所帮助的话可以点赞支持下,Thanks♪(・ω・)ノ

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值