多线程和并发

线程

进程:在操作系统中运行的程序,一个进程可以包含多个线程
程序就是指令和数据的有序集合,静态概念
进程就是执行程序的一次执行过程,动态概念系统资源分配的单元

一个进程中包含多个线程,一个进程至少包含一个线程
线程时cpu调度和执行的单位
线程是独立的执行路径
程序运行时,即使没有自己创建线程。后台也会有多个线程,如主线程,gcxianc
main()是主线程,为系统的入口,用于执行整个程序
在一个进程中如果开辟了多个线程,线程的运行由调度器进行安排调度,调度器与操作系统密切相关,先后顺序不能干预
对同一份资源操作时,会存在资源抢夺的问题,加入并发控制
线程会带来额外的开销
每个线程对自己的工作内存交互,内存控制不当会造成数据不一致

线程创建Thread、Runnable、Callable

继承Thread

1.自定义线程类继承Thread类
重新run()方法,编写线程执行体
创建线程对象,调用start方法();

使用Thread实现网图下载:

package com.ty.threadAndRunableAndCallable;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

public class ThreadDownLoader extends Thread {
    private String url;//网图地址
    private String fileName;//
    public ThreadDownLoader(String url,String fileName){
        this.url = url;
        this.fileName = fileName;
    }

    @Override
    public void run(){
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downLoader(url,fileName);
        System.out.println("文件名:"+ fileName);
    }

    public static void main(String[] args) {
        ThreadDownLoader t1 = new ThreadDownLoader("https://pic.rmb.bdstatic.com/bjh/914b8c0f9814b14c5fedeec7ec6615df5813.jpeg", "1.jpeg");
        ThreadDownLoader t2 = new ThreadDownLoader("https://n.sinaimg.cn/sinakd2020723s/213/w2048h1365/20200723/3918-iwtqvyk4060964.jpg", "2.jpg");
        ThreadDownLoader t3 = new ThreadDownLoader("https://file.ccmapp.cn/group1/M00/16/64/rApntl7CSdeAbpYqABArOjGaasg001.jpg", "3.jpg");

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

class WebDownLoader{
    public void downLoader(String url,String fileName){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(fileName));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO exception!!! downLoader Error!!!");
        }
    }
}

实现Runnable

定义MyRunnable类实现Runnable接口
实现run()方法,编写线程体
创建线程对象,调用start()方法启动线程

Thread和Runnable区别

Thread不建议使用,避免OOP单继承局限,
Runnable具有多线程能力
启动线程:传入目标对象+Thread对象.start()
推荐是哦哟牛皋,避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用

多线程模拟出票:并发问题

package com.ty.threadAndRunableAndCallable;

public class ConcurrencyQuestion implements Runnable {

    private int tickerNums = 10;

    @Override
    public void run() {
        while (true){
            if (tickerNums <= 0){
                break;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"拿到了,第"+(tickerNums--)+"票");
        }
    }

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

}

多线程模拟龟兔赛跑

在这里插入图片描述

package com.ty.threadAndRunableAndCallable;

public class ConcurrencyRace implements Runnable{
    private String winner;
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {

            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子") && i % 2 == 0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            boolean flag = gameOver(i);
            while (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"--->跑了"+i+"步");
        }
    }

    //判断游戏胜利
    private boolean gameOver(int gepts){
        if (winner != null){
            return true;
        }
        if (gepts == 100){
            System.out.println("=============");
            winner = Thread.currentThread().getName();
            System.out.println("winner is "+ winner);
            return true;
        }
        System.out.println("=====w========");
        return false;
    }

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

实现Callable

在这里插入图片描述

package com.ty.threadAndRunableAndCallable;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CallableWebDownLoader implements Callable<Boolean> {
    private String url; //网图地址
    private String name;//保存地址

    public CallableWebDownLoader(String url,String name){
        this.url = url;
        this.name = name;
    }

    @Override
    public Boolean call() throws Exception {
        WebDownLoaderUseCallable callable = new WebDownLoaderUseCallable();
        callable.downLoader(url,name);
        System.out.println("文件名: "+ name);
        return true;
    }

    public static void main(String[] args) {
        CallableWebDownLoader c1 = new CallableWebDownLoader(
                "https://pic.rmb.bdstatic.com/bjh/914b8c0f9814b14c5fedeec7ec6615df5813.jpeg",
                "D:\\software\\JavaCode\\javaSE\\JavaSE01\\src\\com\\ty\\threadAndRunableAndCallable\\1.jpeg");
        CallableWebDownLoader c2 = new CallableWebDownLoader("" +
                "https://n.sinaimg.cn/sinakd2020723s/213/w2048h1365/20200723/3918-iwtqvyk4060964.jpg",
                "D:\\software\\JavaCode\\javaSE\\JavaSE01\\src\\com\\ty\\threadAndRunableAndCallable\\2.jpg");
        CallableWebDownLoader c3 = new CallableWebDownLoader(
                "https://file.ccmapp.cn/group1/M00/16/64/rApntl7CSdeAbpYqABArOjGaasg001.jpg",
                "D:\\software\\JavaCode\\javaSE\\JavaSE01\\src\\com\\ty\\threadAndRunableAndCallable\\3.jpg");

        ExecutorService es = Executors.newFixedThreadPool(1);
        es.submit(c1);
        es.submit(c2);
        es.submit(c3);
        es.shutdown();
    }
}
class WebDownLoaderUseCallable{
    public void downLoader(String url,String name) {
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO Exception, DownLoad Error!!!");
        }
    }
}

静态代理

真是对象和代理对象都要实现一个接口
代理对象要代理真是对象
好处:代理对象可以做真实对象做不了的事情,真实对象可以专注做自己的事情

package com.ty.threadAndRunableAndCallable;

public class staticProxy {
    public static void main(String[] args) {
        HappyCompany happyCompany = new HappyCompany(new You());
        happyCompany.HappyMarry();
    }

}
interface Marry{
    void HappyMarry();
}
//真实角色
class You implements Marry {
    @Override
    public void HappyMarry() {
        System.out.println("结婚了!我们结婚了!!!");
    }
}
//代理角色
class HappyCompany implements Marry{
    private Marry target;
    public HappyCompany(Marry target){
        this.target = target;
    }
    @Override
    public void HappyMarry() {
        after();
        this.target.HappyMarry();
        before();
    }

    private void after() {
        System.out.println("布置现场。。。。");
    }

    private void before() {
        System.out.println("收拾现场。。。。。");
    }
}

线程状态

在这里插入图片描述
五大状态:创建状态、就绪状态、阻塞状态、死亡状态、运行状态

线程执行流程

在这里插入图片描述

线程方法

在这里插入图片描述

停止线程

并不推荐使用stop(),destroy()
推荐线程自己停下来
建议使用一个标志位进行终止变量,flag=false 则终止线程

线程休眠Sleep

在这里插入图片描述

package com.ty.threadAndRunableAndCallable;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CountDown{


    private static void countDown(){
        int count = 10;
        while (true){
            try {
                Thread.sleep(1000);
                count--;
                if (count<0){
                    break;
                }
                System.out.println(count);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        //countDown();
        ControlTime();
    }

    //打印系统时间
    private static void ControlTime(){
        Date date = new Date(System.currentTimeMillis());//打印系统时间
        while (true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:dd:ss").format(date));
                date = new Date(System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

}

线程礼让 yield

在这里插入图片描述

package com.ty.threadAndRunableAndCallable;

public class ThreadYield {
    public static void main(String[] args) {
        Yield yield = new Yield();
        new Thread(yield,"a").start();
        new Thread(yield,"b").start();
    }
}
class Yield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程结束");
    }
}

合并线程Join

待此线程执行结束后,在执行其他线程,其他线程阻塞
可以想象成插队

package com.ty.threadAndRunableAndCallable;

public class ThreadJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("vip 来了"+i);
        }
    }

    public static void main(String[] args) {
        ThreadJoin join = new ThreadJoin();
        Thread thread = new Thread(join);
        thread.start();
        for (int i = 0; i < 20; i++) {
            if (i==10){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main----"+i);
        }
    }
}

线程状态观测

在这里插入图片描述
一个线程可以在给定时间点处于一个状态,这些状态时不反应任何操作系统线程状态的虚拟机状态

package com.ty.threadAndRunableAndCallable;

public class ThreadState {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("=============");
        });

        //观察线程状态
        Thread.State state = thread.getState();
        System.out.println(state); // new

        thread.start();
        state = thread.getState();
        System.out.println(state);//run

        while (state != Thread.State.TERMINATED){//Thread.State.TERMINATED 线程不终止
            try {
                Thread.sleep(100);
                state = thread.getState();
                System.out.println(state);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程优先级

在这里插入图片描述

package com.ty.threadAndRunableAndCallable;

public class ThreadPriority {
    public static void main(String[] args) {
        getThreadPriority getThreadPriority = new getThreadPriority();
        Thread t1 = new Thread(getThreadPriority);
        Thread t2 = new Thread(getThreadPriority);
        Thread t3 = new Thread(getThreadPriority);
        Thread t4 = new Thread(getThreadPriority);
        Thread t5 = new Thread(getThreadPriority);
        Thread t6 = new Thread(getThreadPriority);

        t1.setPriority(1);
        t1.start();
        t2.setPriority(3);
        t2.start();
        t3.setPriority(5);
        t3.start();
        t4.setPriority(7);
        t4.start();
        t5.setPriority(Thread.MAX_PRIORITY);
        t5.start();
        t6.start();

    }
}
class getThreadPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-----"+Thread.currentThread().getPriority());
    }
}

守护线程demon

在这里插入图片描述

线程同步

并发:多个线程同时操作同一资源
由于同一进程的多个线程共享同一块内存空间,方便的同时也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,访问时,加入锁机制synchronized,当一个线程获得对象的排他锁,独占资源时,其他线程必选等待,使用后释放锁即可。
在这里插入图片描述

同步方法

在这里插入图片描述
在这里插入图片描述

同步块

同步块:synchronized() {}
在这里插入图片描述

同步块解决买票、银行取钱、集合不安全问题

//买票
package com.ty.threadAndRunableAndCallable.notSoft;

public class ThreadNotSoftQuestion {
    public static void main(String[] args) {
        buyTicket buyTicket = new buyTicket();
        new Thread(buyTicket,"aa").start();
        new Thread(buyTicket,"bb").start();
        new Thread(buyTicket,"cc").start();

    }
}

class buyTicket implements Runnable{

    private int num= 10;
    private boolean flag = true;

    @Override
    public void run() {
        while (flag){
            buy();
        }
    }

    private synchronized void buy(){
        if (num <= 0){
            flag = false;
            return;
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"---抢到了第 "+(num--)+"张票");
    }
}

package com.ty.threadAndRunableAndCallable.notSoft;

//银行取钱:两个人取钱
public class NotSoftBank {
    public static void main(String[] args) {
        Account account = new Account(100, "基金");
        Drawing you = new Drawing(account,50,"tt");
        Drawing girlFriend = new Drawing(account, 100, "yy");
        you.start();
        girlFriend.start();
    }
}
//银行
class Account{
    int money;// 余额
    String name;//卡号

    public Account(int money,String name){
        this.money = money;
        this.name = name;
    }
}

//模拟取钱
class Drawing extends Thread{
    private Account account;//账户
    private int drawingMoney;//取了多少
    private int nowMoney;//现在剩余

    public Drawing(Account account,int drawingMoney,String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;

    }

    @Override
    public void run(){

        synchronized (account){

            if (account.money - drawingMoney < 0){
                System.out.println(Thread.currentThread().getName()+"余额不足,无法取出");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //余额
            account.money = account.money - drawingMoney;
            //手里
            nowMoney = nowMoney + drawingMoney;
            System.out.println(account.name+"余额为:"+account.money);
            System.out.println(this.getName()+"手里的钱:"+nowMoney);
        }
    }
}


package com.ty.threadAndRunableAndCallable.notSoft;

import java.util.ArrayList;
import java.util.List;

public class NotSoftList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

死锁

在这里插入图片描述

避免死锁的四个方法

在这里插入图片描述

锁 Lock

在这里插入图片描述

synchronized和lock的区别

在这里插入图片描述

线程协作

##线程通信
在这里插入图片描述
均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则就会抛出异常。

线程池

在这里插入图片描述
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间会终止
在这里插入图片描述

package com.ty.threadAndRunableAndCallable;

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

//线程池
public class pool {
    public static void main(String[] args) {
        poolTest poolTest = new poolTest();
        ExecutorService es = Executors.newFixedThreadPool(10);
        es.submit(poolTest);
        es.submit(poolTest);
        es.submit(poolTest);
        es.submit(poolTest);
        es.shutdown();
    }
}
class poolTest implements Runnable{

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/410351.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

图解KMP算法

目录 1.最长公共前后缀1.1前缀1.2后缀1.3最长公共前后缀 2、KMP算法过程2.1例子12.2例子22.3Python代码&#xff1a;2.4next数组的计算过程 1.最长公共前后缀 1.1前缀 前缀说的是一个字符串除了最后一个字符以外&#xff0c;所有的子串都算是前缀。 前缀字符串&#xff1a;A…

KubeSphere实战

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 知…

49.仿简道云公式函数实战-文本函数-Ip

1. Ip函数 获取当前用户的ip地址 注意是Ipv4的地址 2. 函数用法 IP() 3. 函数示例 获取当前用户的ip地址IP() 4. 代码实战 首先我们在function包下创建text包&#xff0c;在text包下创建IpFunction类&#xff0c;代码如下&#xff1a; package com.ql.util.express.sel…

11:日志分析系统ELK|Elasticsearch|kibana

日志分析系统ELK&#xff5c;Elasticsearch&#xff5c;kibana 日志分析系统ELKELK概述Elasticsearch安装Elasticsearch部署Elasticsearch集群Elasticsearch插件 熟悉Elasticsearch的API调用_cat API创建 tedu 索引使用 PUT 方式增加数据查询数据修改数据删除数据 KibanaKibana…

(挖坑) Python调用图工具

基本效果 输入 #!/usr/bin/env pythonThis example demonstrates a simple use of pycallgraph.from pycallgraph import PyCallGraph from pycallgraph.output import GraphvizOutputclass Banana:def eat(self):passclass Person:def __init__(self):self.no_bananas()def…

Xcode与Swift开发小记

引子 鉴于React Native目前版本在iOS上开发遇到诸多问题&#xff0c;本以为搞RN只需理会Javascript开发&#xff0c;没想到冒出CocoaPod的一堆编译问题。所以横下一条心&#xff0c;决定直接进攻iOS本身。不管你是用React Native&#xff0c;还是用Flutter&#xff0c;iOS下的…

算能RISC-V通用云开发空间编译pytorch @openKylin留档

终于可以体验下risc-v了&#xff01; 操作系统是openKylin&#xff0c;算能的云空间 尝试编译安装pytorch 首先安装git apt install git 然后下载pytorch和算能cpu的库&#xff1a; git clone https://github.com/sophgo/cpuinfo.git git clone https://github.com/pytorc…

java农产品商城商城计算机毕业设计包运行调试讲解

jsp mysql农业商城 特效&#xff1a;js产品轮播 功能&#xff1a; 前台&#xff1a; 1.绿色水果 图文列表 详情 2.新闻动态 文章标题列表 详情 3.有机蔬菜 图文列表 详情 4.有机谷物 图文列表 详情 5.有机大米 图文列表 详情 6.用户注册 登陆&#xff08;选择用户和管…

c++ 广度优先搜索(Breadth-First Search,BFS)

广度优先搜索&#xff08;Breadth-First Search&#xff0c;BFS&#xff09;是一种图遍历算法&#xff0c;通常用于搜索或遍历树和图等数据结构。其基本思想是先访问起始顶点&#xff0c;然后逐层遍历其相邻的顶点&#xff0c;直到找到目标顶点或遍历完所有顶点。 BFS通常使用…

前端基础面试题(一)

摘要&#xff1a;最近&#xff0c;看了下慕课2周刷完n道面试题&#xff0c;记录下... 1.请说明Ajax、Fetch、Axios三者的区别 三者都用于网络请求&#xff0c;但维度不同&#xff1a; Ajax&#xff08;Asynchronous Javascript ang XML&#xff09;&#xff0c;是一种在不重新…

xss-跨站脚本攻击漏洞

前备知识&#xff1a; Cookie和Session是Web开发中用于维持用户状态、跟踪用户会话的核心技术&#xff0c;它们的主要目的是在无状态的HTTP协议基础上实现有状态的用户交互。 **Cookie**&#xff1a; - Cookie是一种由服务器发送到客户端&#xff08;通常是用户的浏览器&#x…

【JavaEE】_HttpServlet类

目录 1. init方法 2. destory方法 3. service方法 4. servlet生命周期 前文已经提及到&#xff1a;servlet是tomcat提供的&#xff0c;用于操作HTTP协议的一组API&#xff0c;可以将这组API理解为HTTP服务器的框架&#xff1b; 编写一个servlet程序&#xff0c;往往都要继…

【小尘送书-第十四期】《高效使用Redis:一书学透数据存储与高可用集群》

大家好&#xff0c;我是小尘&#xff0c;欢迎你的关注&#xff01;大家可以一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的工作&#xff01; &#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1…

MySQL 篇-深入了解 DDL 语言(一)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 MySQL 说明 2.0 DDL 语言 2.1 DDL 语言 - 定义数据库 2.1.1 创建数据库操作 2.1.2 查看数据库操作 2.1.3 使用数据库操作 2.1.4 删除数据库操作 2.2 DDL 语言 …

芯片开发erp软件有哪些优势?

随着科技的飞速发展&#xff0c;芯片开发行业正逐渐成为推动科技进步的关键力量。在这一领域中&#xff0c;企业资源规划(ERP)软件的应用正逐渐普及&#xff0c;为芯片开发企业带来了许多显著的优势。下面&#xff0c;我们将详细介绍芯片开发ERP软件的优势。 一、提升管理效率 …

python JZ35 复杂链表的复制(剑指offer)

题目要求: 思路: 思路1&#xff1a;引入dict 思路1&#xff1a;双指针 代码如下: 思路1代码&#xff1a; # -*- coding:utf-8 -*- # class RandomListNode: # def __init__(self, x): # self.label x # self.next None # self.random None …

第十二章 Linux——日志管理

第十二章 Linux——日志管理 基本介绍系统常用日志日志管理服务日志轮替基本介绍日志轮替文件命名logrotate配置文件自定义加入日志轮转应用实例 日志轮替机制原理查看内存日志 基本介绍 日志文件是重要的系统信息文件&#xff0c;其中记录了许多重要的系统事件&#xff0c;包…

Vue.js+SpringBoot开发生活废品回收系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容三、界面展示3.1 登录注册3.2 资源类型&资源品类模块3.3 回收机构模块3.4 资源求购/出售/交易单模块3.5 客服咨询模块 四、免责说明 一、摘要 1.1 项目介绍 生活废品回收系统是可持续发展的解决方案&#xff0c;旨在鼓…

音视频数字化(数字与模拟-电影)

针对电视屏幕,电影被称为“大荧幕”,也是娱乐行业的顶尖产业。作为一项综合艺术,从被发明至今,近200年的发展史中,无人可以替代,并始终走在时代的前列。 电影回放的原理就是“视觉残留”,也就是快速移过眼前的画面,会在人的大脑中残留短暂的时间,随着画面不断地移过,…

【X806开发板试用】PWM呼吸灯、无刷电调、按键测试样例

环境配置 通过我上篇文章&#xff1a;【XR806开发板试用】Ubuntu环境配置&#xff0c;将配置摸清楚后&#xff0c;就可以开始愉快的编写代码了&#x1f604; 视频演示 https://www.bilibili.com/video/BV1NF411B74o/?aid295027788&cid467687216&page1 https://www…