Java多线程+线程池图文实例操作(源码自取)

目录

线程相关概念

并发

 并行

继承Thread类

实现Runnable接口

实现Callable接口

使用ExecutorService 和线程池

多线程卖手机

非同步

同步机制卖手机

锁方法 

锁代码块

​编辑锁静态方法 

锁静态代码块

线程常用方法 

用户线程和守护线程

线程状态

线程池

自定义线程池

java内置线程池

newCacheThreadPool

newFixedThreadPool

 newSingleThreadExecutor

newScheduledThreadPool(int corePoolSize)

Future

模拟案例场景

手机秒杀

银行取款 

练习源码

线程相关概念

Java多线程是Java编程语言中一个重要的特性,它允许程序同时执行多个线程,从而实现并发执行任务,提高程序的性能和响应性。

基础概念

  • 线程: 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。Java中,每一个线程都是一个独立的执行流,拥有自己的堆栈和局部变量。

  • 并发与并行: 并发指的是在同一时间段内,多个任务交替执行,看起来像是同时执行的;并行则是指真正的同时执行多个任务,这通常需要多核处理器的支持。

并发

并发是在同一个时刻,多个任务在交替执行,一种"貌似同时"在执行的错觉,因为计算机执行速度太快,所以会产生这样的错觉,其实就是 单核cpu实现的多任务就是并发

 并行

在同一个时刻,多个任务同时进行,多核cpu可以实现并行

继承Thread类

  • 创建一个新的类继承 Thread 类,并重写 run() 方法。在 run() 方法中编写线程要执行的代码。
  • 优点:可以直接访问并扩展 Thread 类的方法和属性,如 interrupt() 或 getState()
  • 缺点:如果想要扩展其他类,就不能使用此方法,因为Java不支持多重继承。

java中普通类继承Thread可以实现多线程,Thread类其实也是实现了Runnable接口来实现多线程的

启动查看效果

 可以看到打印的日志信息线程名字是在子线程和main线程之间来回进行切换,从而得出并发的效果

而run方法其实只是一个普通方法,使用start()方法才会启动线程的并发

如果使用的是run方法看下是怎样的效果

启动查看

 可以看到并没有交替执行,且子线程打印的线程名也一直是main的名字,run方法只是一个普通方法,并不会启动多线程

实现Runnable接口

  • 创建一个新的类实现 Runnable 接口,并实现 run() 方法。
  • 将这个 Runnable 实例传递给 Thread 构造函数创建 Thread 对象,然后调用 start() 方法启动线程。
  • 优点:可以让你的类继承自其他类,同时实现多线程功能。
  • 缺点:相比直接继承 Thread 类,你必须额外创建一个 Thread 对象。

由于java是单继承多实现,当我们使用了java中某一个类,而当前类又继承了某一个父类,此时如果想再用该类去实现多线程就不能再去继承Thread类来进行实现多线程了,因为java是单继承多实现的

可以采用实现Runnable接口来实现多线程

查看效果

实现Callable接口

  • Callable 和 Runnable 类似,但是 Callable 的 call() 方法可以返回一个结果并且可以抛出异常。
  • 需要创建一个 Callable 接口的实现类,然后将其封装进 FutureTask 中,最后将 FutureTask 传给 Thread 构造函数或直接交给 ExecutorService 执行。
  • 优点:提供了一种可以获取线程执行结果的方式。
  • 缺点:实现比 Runnable 更复杂。

import java.util.concurrent.*;public class IphoneCallable implements Callable<Integer> {// 定义手机数量为100台private static int count = 100;private static final Object lock = new Object();@Overridepublic Integer call() throws Exception {int sold = 0;while (true) {synchronized (lock) {if (count <= 0) {System.out.println(Thread.currentThread().getName()+ " 库存为0, 停止售卖");return sold;}System.out.println(Thread.currentThread().getName()+ " Callable 窗口卖手机,剩余手机数量:" + --count);sold++;Thread.sleep(10);}}}public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executor = Executors.newFixedThreadPool(3);Future<Integer>[] futures = new Future[3];for (int i = 0; i < 3; i++) {futures[i] = executor.submit(new IphoneCallable());}int totalSold = 0;for (Future<Integer> future : futures) {totalSold += future.get();}System.out.println("总共售出手机:" + totalSold);executor.shutdown();}
}

 可以看到获取了线程最后的返回值

使用ExecutorService 和线程池

  • ExecutorService 是 Java 提供的一种用于管理和控制线程的高级工具,它可以复用一组线程来执行任务。
  • 通过 Executors 工厂方法创建 ExecutorService 实例,然后使用 submit() 方法提交 Runnable 或 Callable 任务。
  • 优点:可以更有效地管理线程资源,避免了频繁创建和销毁线程的开销,提高了系统性能。
  • 缺点:相比前三种方法,使用 ExecutorService 的代码更为复杂,且需要理解线程池的工作原理。
  • import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;public class IphoneRunnable implements Runnable {// 定义手机数量为100台private static int count = 100;private static final Object lock = new Object();private static final int THREAD_COUNT = 3;private static volatile boolean shouldRun = true;@Overridepublic void run() {while (shouldRun) {synchronized (lock) {if (count <= 0) {System.out.println(Thread.currentThread().getName()+ " 库存为0, 停止售卖");shouldRun = false;return;}System.out.println(Thread.currentThread().getName()+ " Runnable 窗口卖手机,剩余手机数量:" + --count);try {Thread.sleep(10);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}}}public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);for (int i = 0; i < THREAD_COUNT; i++) {executor.execute(new IphoneRunnable());}executor.shutdown();}
    }

多线程卖手机

非同步

 下面以多个窗口卖手机为例,查看下多线程情况下卖手机会有什么问题

启动查看结果

 发现当手机库存为0后Thread-2和Thread-0还在进行卖,从而出现了-1的超卖现象,这种情况下肯定是不允许的

下面以实现Runnable接口查看现象看下

 也出现了超卖现象

同步机制卖手机

锁方法 

 启动测试

多次测试没有出现超卖现象 

锁代码块

synchronoized不仅可以写在方法上,还可以单独包裹代码块

或者定义一个对象,然后锁该对象,要保证锁的是同一个对象即可

锁静态方法 

如果是静态方法,则锁的是当前的类本身

锁静态代码块

锁静态方法的代码块,也是锁定当前类本身

线程常用方法 

  1. start(): 启动线程,使其开始执行线程的任务。
  2. run(): 线程的任务代码,定义在线程中要执行的操作。
  3. join(): 在一个线程中调用另一个线程的join()方法,会让当前线程等待被调用线程执行完毕后再继续执行。
  4. sleep(long milliseconds): 使线程暂停执行指定的时间,以毫秒为单位。
  5. yield(): 使当前线程让出CPU执行权,让同优先级的线程有机会执行。
  6. interrupt(): 中断线程,发送一个中断信号给线程,使其退出阻塞状态。
  7. isAlive(): 检测线程是否还存活。
  8. setPriority(int priority): 设置线程的优先级,优先级越高,被执行的可能性越大。
  9. getPriority():获取线程优先级

用户线程和守护线程

用户线程: 也叫工作线程,当线程的任务执行完成或通知方式结束

守护线程: 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束

常见的守护线程:垃圾回收机制

先来看下没有守护线程的情况下主线程和子线程的执行关系

启动查看

 可以看到当主线程停止了辛苦的工作后,子线程还在执行愉快的玩耍,没有和主线程共同进退,此时就需要使用守护进程了

再次启动查看效果

 可以看到当设置子线程为守护线程后,主线程停止后,子线程也自动停止了执行

线程状态

  1. NEW(新建): 当线程对象被创建时,它处于新建状态。此时线程尚未开始执行。
  2. RUNNABLE(可运行 可细分为 ready(准备) 和runing(运行)): 线程对象创建后,其他线程调用了该对象的start()方法,线程进入可运行状态。处于可运行状态的线程可能正在等待CPU的调度执行,也可能正在执行。
  3. BLOCKED(阻塞): 当线程在等待获取同步锁时,如果获取不到(因为其他线程已经持有了锁),该线程会进入阻塞状态。
  4. WAITING(等待): 线程进入等待状态,等待其他线程的通知或者唤醒。
  5. TIMED_WAITING(计时等待): 线程进入计时等待状态,等待一定的时间后自动唤醒。
  6. TERMINATED(终止): 线程执行完任务或者因异常退出后,进入终止状态

看下线程状态演化图

代码打印其各个状态

 

 

 Waitiing状态没有打印出来,可以自己尝试下

线程池

线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。这里的线程就是前面的线程,任务就是实现了Runnable或Callable接口的实例对象;

使用线程池最大的原因就是可以根据系统的需求和硬件环境灵活的控制线程的数量且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力;

线程和任务分离,提升线程重用性;

控制线程并发数量,降低服务器压力,统一管理所有线程;

提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间

自定义线程池

自定义线程类

import java.util.List;/*需求:编写一个线程类,需要继承Thread类,设计一个属性,用于保存线程的名字;设计一个集合,用于保存所有的任务;*/
public class MyWorker extends Thread{private String name;//保存线程的名字private List<Runnable> tasks;//利用构造方法,给成员变量赋值public MyWorker(String name, List<Runnable> tasks) {super(name);this.tasks = tasks;}@Overridepublic void run() {//判断集合中是否有任务,只要有,就一直执行任务while (tasks.size()>0){Runnable r = tasks.remove(0);r.run();}}
}

自定义任务类


/*需求:自定义线程池练习,这是任务类,需要实现Runnable;包含任务编号,每一个任务执行时间设计为0.2秒*/
public class MyTask implements Runnable{private int id;//由于run方法是重写接口中的方法,因此id这个属性初始化可以利用构造方法完成private static boolean loop = true;private static int count=100;public void setId(int id){this.id = id;}@Overridepublic void run() {// 判断手机数量是否大于0while (loop) {extracted();}}private  synchronized static void extracted() {if(count<=0){System.out.println(Thread.currentThread().getName()+ "库存为0,停止售卖");loop = false;return;}System.out.println(Thread.currentThread().getName()+ "Runnable窗口卖手机,剩余手机数量:" + --count);try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}}@Overridepublic String toString() {return "MyTask{" +"id=" + id +'}';}
}

自定义线程池


import java.util.Collections;
import java.util.LinkedList;
import java.util.List;/*这是自定义的线程池类;成员变量:1:任务队列   集合  需要控制线程安全问题2:当前线程数量3:核心线程数量4:最大线程数量5:任务队列的长度成员方法1:提交任务;将任务添加到集合中,需要判断是否超出了任务总长度2:执行任务;判断当前线程的数量,决定创建核心线程还是非核心线程*/
public class MyThreadPool {// 1:任务队列   集合  需要控制线程安全问题private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());//2:当前线程数量private int num;//3:核心线程数量private int corePoolSize;//4:最大线程数量private int maxSize;//5:任务队列的长度private int workSize;public MyThreadPool(int corePoolSize, int maxSize, int workSize) {this.corePoolSize = corePoolSize;this.maxSize = maxSize;this.workSize = workSize;}//1:提交任务;public void submit(Runnable r){//判断当前集合中任务的数量,是否超出了最大任务数量if(tasks.size()>=workSize){System.out.println("任务:"+r+"被丢弃了...");}else {tasks.add(r);//执行任务execTask(r);}}//2:执行任务;private void execTask(Runnable r) {//判断当前线程池中的线程总数量,是否超出了核心数,if(num < corePoolSize){new MyWorker("核心线程:"+num,tasks).start();num++;}else if(num < maxSize){new MyWorker("非核心线程:"+num,tasks).start();num++;}else {System.out.println("任务:"+r+" 被缓存了...");}}}

测试

/*测试类:1: 创建线程池类对象;2: 提交多个任务*/
public class MyTest {public static void main(String[] args) {//1:创建线程池类对象;MyThreadPool pool = new MyThreadPool(2,4,20);//2: 提交多个任务MyTask my = new MyTask();for (int i = 0; i <30 ; i++) {//3:创建任务对象,并提交给线程池my.setId(i);pool.submit(my);}}
}

启动测试:

可以看到自定义的线程池也没有出现超卖

java内置线程池

java中使用ExecutorService可以利用jdk中的Executors类中的静态方法获取线程池

newCacheThreadPool

  • 作用: 创建一个可缓存的线程池,线程数动态调整。
  • 优点: 当任务完成后,空闲线程会被回收,当需要时,又可以迅速恢复线程,适用于执行大量短时间的任务。

创建一个默认的线程池对象 里面的线程可重用

还可以传递自定义的线程工厂来实现

 

newFixedThreadPool

  • 作用: 创建一个固定大小的线程池。
  • 优点: 线程数量固定,可以有效控制资源使用,避免大量线程创建和销毁带来的性能开销。

创建可重用固定线程数的线程池

 同样也可以指定线程数

测试

 newSingleThreadExecutor

  • 作用: 创建一个单线程化的线程池。
  • 优点: 确保所有任务按照指定顺序执行,可以用于需要保持任务执行顺序的场景。

创建一个使用的那个worker线程的Executor,以误界队列方式以来运行该线程

也可以指定线程工厂来进行创建任务

 

也是只有一个线程 

newScheduledThreadPool(int corePoolSize)

  • 作用: 创建一个定时的线程池,支持定时和周期性任务执行。
  • 优点: 可以执行定时任务,非常适合于需要定期执行的任务场景。

启动查看效果

可以看到程序先输出了over,有了2秒延迟后才开始执行任务

Future

Future 是 Java 中用于表示异步计算结果的一个接口,它通常与 ExecutorServiceCallable 结合使用

当我们在执行异步任务时,有时会遇到需要拿取任务返回值的情况,此时就需要用Future和Callable接口来进行任务书写

模拟案例场景

手机秒杀

 创建线程任务

/*任务类:包含了商品数量,客户名称,送手机的行为;*/
public class Mytask implements Runnable {//设计一个变量,用于表示商品的数量private static int id = 10;//表示客户名称的变量private String userName;public Mytask(String userName) {this.userName = userName;}@Overridepublic void run() {String name = Thread.currentThread().getName();System.out.println(userName+"正在使用"+name+"参与秒杀任务...");try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}synchronized (Mytask.class){if(id>0){System.out.println(userName+"使用"+name+"秒杀:"+id-- +"号商品成功啦!");}else {System.out.println(userName+"使用"+name+"秒杀失败啦!");}}}
}

创建测试入口

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*主程序类,测试任务类*/
public class Mytest {public static void main(String[] args) {//1:创建一个线程池对象ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,1,TimeUnit.MINUTES,new LinkedBlockingQueue<>(15));//2:循环创建任务对象for (int i = 1; i <=20 ; i++) {Mytask myTask = new Mytask("客户"+i);pool.submit(myTask);}//3:关闭线程池pool.shutdown();}
}

启动测试

银行取款 

编写线程任务

public class MyTask implements Runnable {//用户姓名private String userName;//取款金额private double money;//总金额private static double total = 1000;public MyTask(String userName, double money) {this.userName = userName;this.money = money;}@Overridepublic void run() {String name = Thread.currentThread().getName();System.out.println(userName+"正在准备使用"+name+"取款:"+money+"元");try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}synchronized (MyTask.class){if(total-money>0){System.out.println(userName+"使用"+name+"取款:"+money+"元成功,余额:"+(total-money));total-=money;}else {System.out.println(userName+"使用"+name+"取款:"+money+"元失败,余额:"+total);}}}
}

启动入口

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;public class MyTest {public static void main(String[] args) {//1:创建线程池对象ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {int id = 1;@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "ATM" + id++);}});//2:创建两个任务并提交for (int i = 1; i <=2 ; i++) {MyTask myTask = new MyTask("客户" + i, 800);pool.submit(myTask);}//3:关闭线程池pool.shutdown();}
}

 

练习源码

 练习源码

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频监控管理平台的日志功能的重要性

日志功能的重要性 视频监控平台在日常工作生活中越来越重要&#xff0c;具有完备的平台日志&#xff0c;不仅可以增强视频监控系统的自身安全性&#xff0c;还能在更大程度上保障社会的安全与稳定。 &#xff08;一&#xff09;安全保障 视频监控平台作为安全防护…

第三十三章 添加和使用自定义标题元素

文章目录 第三十三章 添加和使用自定义标题元素SOAP 标头元素简介如何表示 SOAP 标头 第三十三章 添加和使用自定义标题元素 本主题介绍如何添加和使用自定义 SOAP 标头元素。 有关发生故障时添加标头元素的信息&#xff0c;请参阅 SOAP 故障处理。 WS-Addressing 标头元素在…

机器学习课程复习——集成学习

1. 基本概念 1.1. 定义 通过构建并结合多个个体学习器来完成学习任务,获得比单一学习器显著优越的泛化性能。 1.2. 分类 名称个体学习器例子同质集成基学习器Boosting、Bagging异质集成组件学习器Stacking1.3. 研究的核心 个体学习器的“准确性”和“多样性”本身就存在冲…

悬浮翻译app免费怎么操作?看完这篇文章就知道了

#高考结束该出发看世界了# 高考的钟声已落&#xff0c;是时候开启探索世界的旅程了。无论是踏上旅途&#xff0c;观看视频&#xff0c;阅读书籍&#xff0c;还是浏览网页资料&#xff0c;我们都有机会拓宽视野。 然而&#xff0c;语言常常成为我们与世界沟通的障碍。好在&…

设计模式学习之——单例模式

文章目录 单例模式什么叫做单例模式单例模式的动机 简单单例模式思考 饿汉式单例和懒汉式单例饿汉式单例懒汉式单例 单例模式总结1&#xff0e;主要优点2&#xff0e;主要缺点3&#xff0e;适用场景 单例模式 什么叫做单例模式 顾名思义&#xff0c;简单来说&#xff0c;单例…

Python语言修改控制台输出文字的颜色和背景颜色

Python语言修改控制台输出文字的颜色和背景颜色 格式显示模式字体颜色背景颜色文字加效果显示类 格式 \033[显示模式;字体颜色;背景颜色m 显示模式 显示模式格式将文本颜色和背景颜色重置为默认值&#xff0c;取消所有其他文本属性\033[0m高亮&#xff08;加粗&#xff09;\03…

Study--Oracle-04-SQL练习

一、SQL语句思维导图 二、SQL练习 -- 以employee_id 为排序&#xff0c;列出前5个人 -- FETCH select employee_id,first_name from employees order by employee_id FETCH FIRST 5 rows only; -- 以employee_id 为排序&#xff0c;从第6个人开始 到第10个人 -- offset …

【Java】已解决java.sql.SQLException异常

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.sql.SQLException异常 在Java中&#xff0c;java.sql.SQLException是一个通用的异常类&#xff0c;用于表示在数据库操作中发生的错误。无论是类型错误、数据类型不匹配…

ArcGIS图斑分区(组)排序—从上到下从左到右

​​ 点击下方全系列课程学习 点击学习—>ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放 ArcGIS图斑分区&#xff08;组&#xff09;从上到下从左到右排序 是之前的内容的升级 GIS技巧100例——12ArcGIS图斑空间排序 关于今天的内容 我们在19年已经和大家分…

【Kubernetes项目部署】k8s集群+高可用、负载均衡+防火墙

项目架构图 &#xff08;1&#xff09;部署 kubernetes 集群 详见&#xff1a;http://t.csdnimg.cn/RLveS &#xff08;2&#xff09; 在 Kubernetes 环境中&#xff0c;通过yaml文件的方式&#xff0c;创建2个Nginx Pod分别放置在两个不同的节点上&#xff1b; Pod使用hostP…

Docker环境离线安装

Docker环境离线安装 下载下列.deb包 sudo *.deb

windows系统中开发的GO程序生成docker镜像并部署到阿里云服务(linux系统)的操作说明

本文简述将go程序生成docker镜像的操作方法&#xff0c;以及如何部署到阿里云服务。其中go程序在windows系统中开发&#xff0c;阿里云服务的操作系统为linux&#xff08;centos7.9&#xff09;&#xff0c;以下为流程示意图&#xff1a; 一、window系统中开发go程序 程序实现…

前端技术栈三(vue+Axios)

一、Vue 1 基本介绍 1.1 Vue 是什么? Vue (读音 /vjuː/&#xff0c;类似于 view) 是一个前端框架, 易于构建用户界面 Vue 的核心库只关注视图层&#xff0c;不仅易于上手&#xff0c;还便于与第三方库或项目整合 支持和其它类库结合使用 开发复杂的单页应用非常方便 Vue 是…

RAM + 串口的简单应用

REVIEW 之前已经学习过&#xff1a; RAM&#xff1a; RAM IP核配置_ip核 ram配置-CSDN博客 串口接收&#xff1a;Vivado 串口接收优化-CSDN博客 串口发送&#xff1a;Vivado 串口通信(UART)------串口发送_vivado串口收发实验-CSDN博客 按键&#xff1a;基于状态机的按键消抖实…

ADOP带你了解:数据中心的高速互联解决方案

随着大语言模型和AIGC的飞速发展&#xff0c;数据中心对于高速、高可靠性的网络连接需求日益增长。ADOP系列产品正是在这样的背景下应运而生&#xff0c;为现代数据中心提供了全面的连接解决方案。 ADOP系列产品概览 ADOP系列产品旨在为云、高性能计算、Web 2.0、企业、电信、…

SparkSQL的分布式执行引擎-Thrift服务:学习总结(第七天)

系列文章目录 SparkSQL的分布式执行引擎 1、启动Thrift服务 2、beeline连接Thrift服务 3、开发工具连接Thrift服务 4、控制台编写SQL代码 文章目录 系列文章目录前言一、SparkSQL的分布式执行引擎(了解)1、启动Thrift服务2、beeline连接Thrift服务3、开发工具连接Thrift服务4、…

(7)摄像机和云台

文章目录 前言 1 云台 2 带有MAVLink接口的摄像机 3 相机控制和地理标签 4 视频质量差的常见修复方法 5 详细主题 前言 Copter、Plane 和 Rover 最多支持 3 轴云台&#xff0c;包括自动瞄准感兴趣区域&#xff08;ROI&#xff09;的相机和自动触发相机快门等先进功能。按…

好书推荐:AI教母李飞飞自传《我看见的世界》,豆瓣9.1分!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

子组件和父组件之间传值#Vue3#defineProps

子组件和父组件之间传值#Vue3#defineProps 效果&#xff1a; 子组件&#xff1a; <!-- 6s执行项详情图片的子组件 --> <template><div><imgv-if"itemsLocal.url":src"itemsLocal.url"style"width: 50px; height: 50px; marg…

【Ardiuno】实验ESP32单片机搭建简易Web服务器功能(图文)

今天&#xff0c;小飞鱼继续来测试使用ESP32来实现简易的wifi无线web服务器功能。使用Ardiuno平台编辑器输入以下示例代码&#xff1a; #include <WiFi.h> #include <WiFiClient.h> #include <WebServer.h> #include <ESPmDNS.h>const char* ssid &q…