【JavaEE -- 多线程3 - 多线程案例】

多线程案例

  • 1.单例模式
    • 1.1 饿汉模式的实现方法
    • 1.2 懒汉模式的实现方法
  • 2. 阻塞队列
    • 2.1 引入生产消费者模型的意义:
    • 2.2 阻塞队列put方法和take方法
    • 2.3 实现阻塞队列--重点
  • 3.定时器
    • 3.1 定时器的使用
    • 3.2 实现定时器
  • 4 线程池
    • 4.1 线程池的使用
    • 4.2 实现一个简单的线程池 -- 重点

1.单例模式

单例模式:是一种设计模式,某个类,在一个进程中只创建出一个实例(对象),对代码进行一个更严格的校验和检查。

实现单例模式最基础的实现方式

  • 饿汉模式:
  • 懒汉模式

1.1 饿汉模式的实现方法

单例模式中一种简单的写法,饿汉:形容创建实例非常迫切,实例是在类加载的时候就创建了,创建时机非常早,相当于程序一启动,实例就创建了

class Singleton {// 在这个Singleton 被加载的时候,就会初始化这个静态成员private static Singleton instance = new Singleton();// instance 指向的这个对象,就是唯一的一个对象public static Singleton getInstance() {// 对于饿汉模式,getInstance直接返回Instance实例,这个操作本质上是读操作,在多线程情况下读取同一个变量是线程安全的return instance;}private Singleton() {}
}public class ThreadDemo26 {public static void main(String[] args) {// Singleton singleton = new Singleton(); //Singleton s = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s == s2);}
}

1.2 懒汉模式的实现方法

懒汉模式:创建实例的时机比较晚,只到第一次使用的时候才会创建实例
注意

  • 在这个引用 指向唯一实例,这个引用先初始化为null,而不是立即创建实例
  • 由于在 instance = new SingletonLazy(); 实例化的时候有读有写,在多线程下是不安全的,会出现指令重排序的线程安全问题,通过添加volatile解决
  • instance = new SingletonLazy(); 拆成三大步骤:
    1. 申请一段内存空间
    2. 在这内存上调用构造方法,创建出这个实例
    3. 把这个内存地址赋值给Instance引用变量
      正常是123,但是在多线程下可能132,就会出现问题
  • 如果InStance为null,就说明首次调用,首次调用就需要考虑到线程安全问题,如果非null,就说明是后续的调用,就不必加锁,即双重校验锁

** volatile**:

  1. 保证内存可见性,每次访问变量都必须重新读取内存,而不会优化到寄存器/缓存中
  2. 禁止指令重排序,针对这个volatile修饰的变量的读写操作相关指令,是不能被重排序的
class SingletonLazy {// 这个引用指向唯一实例,这个引用先初始化为null,而不是立即创建实例// 在这里添加volatile 避免重排序引起的线程安全问题private volatile static SingletonLazy instance = null;private static Object locker = new Object();// 在懒汉模式,有读也有写 instance = new SingletonLazy();,在多线程下是不安全的,且不是单例模式了,// 1.通过加锁 synchronized  然后再 把if 和 new两个操作打包成一个原子的public static SingletonLazy getInstance() {// 2.如果Istance 为null,就说明首次调用,首次调用就需要考虑到线程安全问题// 如果非null,就说明是后续的调用,就不必加锁// 双重校验锁if (instance == null) {synchronized (locker) {if (instance == null) {instance = new SingletonLazy();// 3.还会出现指令重排序引起线程安全,通过添加volatile解决/**instance = new SingletonLazy(); 拆成三大步骤* 1.申请一段内存空间* 2.在这个内存上调用构造方法,创建出这个实例* 3.把这个内存地址赋值给Instance引用变量* 正常是123,但是在多线程下可能132 就会出现问题*/}}}return instance;}private SingletonLazy() {}
}
public class ThreadDemo27 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);}
}

2. 阻塞队列

阻塞队列是一种特殊的队列,遵守先进先出的原则
阻塞队列是一种线程安全的数据结构,包含两个特性

  • 当队列满的时候,继续入队列就会阻塞,直到有其他线程从队列中取走元素
  • 当队列空的时候,继续出队列也会阻塞,直到有其他线程往队列中插入元素

基于阻塞队列,就可以实现 生产消费者模型(是一种多线程编程的方法)

2.1 引入生产消费者模型的意义:

  1. 解耦合,即把代码的耦合程度,从高降低,在实际开发中,经常涉及到分布式系统服务器整个功能不是由一个服务器全部完成的,而是每个服务器负责一部分功能,通过服务器之间的网络通信,最终完成整个功能
    公网内 的电商网站客户端,获取到主页信息,机房内部网络中 有入口服务器A,用户服务器B,商品服务器C。在这个模型中,A代码就需要涉及到一些和B相关的操作,同样B也涉及A,A和C中的代码也相互涉及。如果B或者C挂了,对A的影响非常大,即为高耦合
    在这里插入图片描述

引入生产消费者模型之后就可降低耦合,即添加阻塞队列
在这里插入图片描述上述模型中,A和B、C都不是直接交互了,而是通过阻塞队列传话,如果B或者C挂了,对A的影响几乎没有

  1. 削峰填谷:如下图模型,当请求多了,A的请求数量会增加很多,B用户服务器(找到对应用户信息)和C商品服务器(从数据库中匹配商品)都会有很大的影响。
    在这里插入图片描述
    添加阻塞队列之后:即使外界的请求出现峰值,队列没有业务逻辑,只是存储数据抗压能力很强。有效的防止了B和C被冲击挂掉
    在这里插入图片描述

2.2 阻塞队列put方法和take方法

  • put(): put和offer都是入队列,而put带有阻塞功能,没带阻塞功能,队列满了会返回结果。
  • take():取出元素的时候,带有阻塞功能,判定如果队列为空,就进行wait阻塞等待。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;public class ThreadDemo28 {public static void main(String[] args) throws InterruptedException {BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);// put和offer都是入队列 ,而 put带有阻塞功能,没带阻塞,队列满了会返回结果queue.put("aaa");// take 也带有阻塞功能String elem = queue.take();System.out.println("elem = " + elem);elem = queue.take();System.out.println("elem = " + elem);}
}

2.3 实现阻塞队列–重点

1)先实现普通队列,基于数组来实现(环形队列),区分队列空和队列满

  1. 浪费一个格子,定义一个head队头和tail队尾,tail最多走到head的前一个位置
  2. 引入size变量

2)再加上线程安全
3)再加上阻塞功能

  • 队列满了,添加wait进行阻塞,队列不满,即出队列成功后进行notify唤醒
  • 队列空了,再出队列,同样也需要阻塞,同样是在另一个入队列成功后的线程中唤醒
class MyBlockingQueue {private String[] elems = null;private int head = 0;private int tail = 0;private int size = 0;public MyBlockingQueue(int capacity) {elems = new String[capacity];}private Object locker = new Object();public void put(String elem) throws InterruptedException {synchronized (locker) {// 使用while:wait可能会被提前唤醒(当条件还没满足,就被唤醒了)while (size >= elems.length) {// 队列满了 实现阻塞locker.wait(); //在Java标准库推荐使用wait搭配while循环,多一/N次确认操作}// 如果加锁不包含 while判断队列是否为满,在多线程下就会导致当入队列就会多入一个// 新的元素要放到 tail指向的位置上elems[tail] = elem;tail++;// 如果队尾大于数组大小,让tail重新指向0下标,形成闭环if (tail >= elems.length) { // tail = tail % elems.lengthtail = 0;}size++;// 入队列成功后进行唤醒locker.notify();}}public String take() throws InterruptedException {String elem = null;synchronized (locker) {while (size == 0) {//队列空了// 实现阻塞locker.wait();}// 取出 head 位置的元素并返回elem = elems[head];head++;if (head >= elems.length) {head = 0;}size--;// 队列不满,即出队列成功之后,加上唤醒locker.notify();}return elem;}}
public class ThreadDemo29 {public static void main(String[] args) throws InterruptedException {MyBlockingQueue queue = new MyBlockingQueue(1000);// 生产者消费者模型(核心是阻塞队列,使用synchronized和wait/notify达到线程安全&阻塞) 不仅仅是一个线程// 也可能是一个独立的服务器程序,甚至是一组服务器程序// 生产者Thread t1 = new Thread(() -> {int n = 1;while (true) {try {queue.put(n+" ");System.out.println("生产元素:" + n);n++;//Thread.sleep(500); //生产一个消费一个} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 消费者Thread t2 = new Thread(() -> {while (true) {try {String n = queue.take();System.out.println("消费元素:" + n);Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();}
}

3.定时器

在Java标准库中提供了定时器的使用,Timer类,核心方法是schedule(),它有两个参数,第一个参数是即将执行的任务代码,第二个是指定多长时间之后执行(单位ms)

3.1 定时器的使用

import java.util.Timer;
import java.util.TimerTask;
// 运行完之后,进程没有结束,因为timer 里内置了线程(前台线程) timer不知道是否还要添加任务进来,
// 可以使用timer.cancel()来主动结束public class ThreadDemo30 {public static void main(String[] args) throws InterruptedException {// 定义一个timer添加多任务,每个任务同时会带有一个时间Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {//时间到了之后,要执行的代码System.out.println("hello timer 3000");}},3000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello timer 2000");}},2000);timer.schedule(new TimerTask() {@Overridepublic void run() {System.out.println("hello timer 1000");}},1000);System.out.println("hello main");Thread.sleep(3000);timer.cancel();}
}

3.2 实现定时器

  • 一个带优先级的阻塞队列(按从小到大的顺序)
  • 队列中的每个元素是一个Task对象
  • Task中带有一个时间属性,队首元素就是即将执行的
  • 同时有一个worker线程一直扫描队首元素,看队首元素是否需要执行
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Timer;// 通过这个类,来描述一个任务
class MyTimerTask implements Comparable<MyTimerTask> {//在什么时间点来执行这个任务// 此处约定这个time是一个ms 级别的时间戳private long time;public long getTime() {return time;}// 实际任务要执行的代码private Runnable runnable;// delay 期望是一个相对时间public MyTimerTask(Runnable runnable, long delay) {this.runnable = runnable;// 计算真正要执行任务的绝对时间()this.time = System.currentTimeMillis() + delay;}public void run() {runnable.run();}@Overridepublic int compareTo(MyTimerTask o) {return (int) (this.time - o.time);}
}// 通过这个类,来表示一个定时器
class MyTimer {// 负责扫描任务队列,执行任务的线程private Thread t = null;// 任务队列private PriorityQueue<MyTimerTask> queue = new PriorityQueue<>();private Object locker = new Object();// 把任务放进队列public void schedule(Runnable runnable,long delay) {synchronized (locker) {MyTimerTask task = new MyTimerTask(runnable,delay);queue.offer(task);// 添加新的元素之后,就可以唤醒扫描线程的wait了locker.notify();}}public void cancel() {//}// 构造方法,创建扫描线程,让扫描线程来完成判定和执行public MyTimer() {t = new Thread(() -> {// 扫描线程就需要循环反复的扫描队首元素,然后判定队首元素是不是时间到了// 如果没到时间,啥也没有// 如果时间到了,就执行这个任务从队列中删除while (true) {try {// 1. 解决线程安全问题synchronized (locker) { //这里的代码执行速度很快,解锁之后立即又重新尝试加锁,导致// 其他线程通过schedule想加锁,但是加不上 (即线程饿死) -》 引入wait/notifyif (queue.isEmpty()) {// 暂时先不处理locker.wait();}MyTimerTask task = queue.peek();// 获取当前时间long curTime = System.currentTimeMillis();if (curTime >= task.getTime()) {//当前时间已经达到了任务时间,就可以执行任务了queue.poll();task.run();}else {// 当前时间还没到任务时间,暂时不执行// 不能使用sleep。会错过新的任务,也无法释放锁//Thread.sleep(task.getTime() - curTime);locker.wait(task.getTime() - curTime);}}  // 释放锁的} catch (InterruptedException e) {e.printStackTrace();}}});// 启动线程t.start();}
}
public class ThreadDemo31 {public static void main(String[] args) {MyTimer timer = new MyTimer();timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 3000");}},3000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 2000");}},2000);timer.schedule(new Runnable() {@Overridepublic void run() {System.out.println("hello 1000");}},1000);System.out.println("hello main");}
}

4 线程池

线程池:提前把需要用的线程,在线程池里准备好,需要用的时候就从池子里取,用完之后还给池子
由于频繁的创建销毁进程,成本太高,引入了轻量级 进程,即线程,如果创建销毁线程的频率也进一步提高,此时线程的创建销毁开销也越来越大
所以有两种优化此处的线程的创建和销毁:

  1. 引入轻量级 线程,即纤程/协程:本质是程序员在用户太代码进行调度,而不是靠内核的调度器调度,节省了调度上的开销
  2. 线程池:把要使用的线程提前创建好,用完了之后也不要直接释放,而是放进线程池里以备下次使用,从而节省了创建销毁线程的开销。

引入问题:为什么从线程池里取线程就比系统申请更高效

  • 从线程池里取线程是纯用户态代码(可控的)
  • 通过系统申请创建线程,就是需要内核来完成(不太可控)

4.1 线程池的使用

在Java标准库中,把ThreadPoolExecutor类表线程池,给封装 成 Executors 工厂类,工厂类:创建出不同的线程池对象(在内部把ThreadPoolExecutor创建好了并且设置不同的参数)
ThreadPoolExecutor 线程池的参数:
在这里插入图片描述

  • int corePoolSize :核心线程数,int maximunmPoolSize:最大线程数
  • long keepAliveTime:保持存活时间,TimeUnit unit:时间单位(s,min,ms,hour)
  • ThreadFactory threadFactor:线程工厂,通过这个工厂类来创建线程对象(Thread)
  • RejectExecutionHandler handler:拒绝策略,在线程池中,有一个阻塞队列,能够容纳的元素有上限,当任务队列已经满了,如果继续往队列添加任务,线程池会进行下面4种操作:

在这里插入图片描述

Executor创建线程池的方式:

  • newFixedThreadPool: 创建固定线程数的线程池
  • newCachedThreadPool: 创建线程数目动态增长的线程池.
  • newSingleThreadExecutor: 创建只包含单个线程的线程池.
  • newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadDemo32 {public static void main(String[] args) {// 创建线程池的时候,设定线程池的线程数量ExecutorService service = Executors.newFixedThreadPool(4);service.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello");}});}
}

** 创建线程池的时候,很多时候需要设定线程池的线程数量?**

  • 不同的程序,能够设定的线程的数目是不同的,要具体问题具体分析, 一个线程是CPU密集型的任务(在线程run里面进行计算),还是IO密集型任务(在线程run里使用scanner读取用户的输入)
  • 如果一个进程中所有的线程都是CPU密集型的,每个线程所有工作都是CPU上执行,此时线程数目就不应该超过N(CPU的逻辑核心数)。
  • 如果一个进程中,所有线程都是IO密集型,每个线程的大部分工作都是等待IO,此时线程数目与那元超过N
  • 由于程序的复杂性,所以需要通过实验/测试,即设定不同的线程数目,分别进行性能测试,衡量每种线程数目下,总的时间开销,和系统资源占用的开销,找到这之间的合适值

4.2 实现一个简单的线程池 – 重点

  1. 提供构造方法,指定创建多少个线程
  2. 在构造方法中,把这些线程都创建好
  3. 有一个阻塞队列,能够持有要执行的任务
  4. 提供submit方法,可以添加新的任务
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;class MyThreadPoolExecutor {private List<Thread> threadList = new ArrayList<>();// 保存任务的队列private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);// 通过n来指定创建多少个线程public MyThreadPoolExecutor(int n) {for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {// 线程把任务队列中的任务不停的取出来,并且进行执行while (true) {try {// 此处的take 带有阻塞功能// 如果队列为空,此时 take就会阻塞Runnable runnable = queue.take();// 取出一个执行一个runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();threadList.add(t);}}public void submit(Runnable runnable) throws InterruptedException {queue.put(runnable);}
}public class ThreadDemo33 {public static void main(String[] args) throws InterruptedException {MyThreadPoolExecutor executor = new MyThreadPoolExecutor(4);for (int i = 0; i < 1000; i++) {// n是一个实事final变量 ,每次循环都是一个新的n,就可以被捕获int n = i;executor.submit(new Runnable() {@Overridepublic void run() { // 回调函数访问当前外部作用域的变量就是变量捕获// i 一值在变,把i改成成员变量或者 int n = iSystem.out.println("执行任务" + n + ",当前线程为:"+ Thread.currentThread().getName());}});}}
}

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

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

相关文章

【XR806开发板试用】基于WEBSOCKET实现人机交互(控制开关灯)以及开发问题记录

一、开发板编译、功能介绍 根据官方文档编译烧录成功后&#xff0c;我们修改下官方例子&#xff0c;进行开发来实现websocket。 整体流程&#xff1a;开发板先自动寻找指定的wifi并且连接&#xff0c;连接成功后&#xff0c;通过websocket来与服务端连接&#xff0c;连接成功后…

【SVG】前端-不依靠第三方包怎么画连线???

如何用SVG实现连线功能 在Web开发中&#xff0c;我们经常会遇到需要在页面上绘制图形或者实现一些图形交互的场景。SVG&#xff08;Scalable Vector Graphics&#xff09;作为一种用于描述二维图形的XML标记语言&#xff0c;在这方面提供了极大的便利。本文将以一个具体的例子…

蜡烛图K线图采用PictureBox控件绘制是实现量化交易的第一步非python量化

用vb6.0开发的量化交易软件 VB6量化交易软件的演示视频演示如上 股票软件中的蜡烛图是非常重要的一个东西&#xff0c;这里用VB6.0自带的Picture1控件的Line方法就可以实现绘制。 关于PictureBox 中的line 用法 msdn 上的说明为如下所示 object.Line [Step] …

LabVIEW电液伺服作动器

LabVIEW电液伺服作动器 随着工业自动化技术的快速发展&#xff0c;电液伺服作动器在各类精密控制领域得到了广泛应用。基于CRIO架构&#xff0c;利用LabVIEW软件开发了一套电液伺服作动器测控系统&#xff0c;实现了高精度的位移同步控制与测量&#xff0c;有效提高了系统的控…

3.Gen<I>Cam文件配置

Gen<I>Cam踩坑指南 我使用的是大恒usb相机&#xff0c;第一步到其官网下载大恒软件安装包,安装完成后图标如图所示&#xff0c;之后连接相机&#xff0c;打开软件&#xff0c;相机显示一切正常。之后查看软件的安装目录如图&#xff0c;发现有GenICam和GenTL两个文件&am…

uniapp无感登录封装

全局请求封装 https://blog.csdn.net/qq_42618566/article/details/109308690 无感登录封装 import {http} from "./index.js" let requestsQueue []; // 请求队列// 记录请求队列 export function recordRequests(path, params, loading, method) {requestsQueu…

JVM高频面试点(一):Java类加载过程

1.概述 在 Java 中&#xff0c;类加载过程是指将 Java 类的字节码加载到内存中&#xff0c;并转换为 Java 虚拟机能够识别和执行的数据结构的过程。类加载是 Java 虚拟机执行 Java 程序的必要步骤之一&#xff0c;它负责加载程序中用到的类和接口。下图所示是 ClassLoader 加载…

鸿蒙车载原生开发,拓展新版图

一天内连发“五弹”、HiCar 4.0首次上车 华为鸿蒙狂扩“汽车朋友圈”-上游新闻 汇聚向上的力量 3月15日&#xff0c;在“华为云&华为终端云服务创新峰会2024”上&#xff0c;华为首批汽车行业伙伴广汽传祺、岚图汽车、零跑汽车、凯翼汽车加入鸿蒙生态合作&#xff0c;华为…

FPGA高端项目:FPGA基于GS2971+GS2972架构的SDI视频收发+OSD动态字符叠加,提供1套工程源码和技术支持

目录 1、前言免责声明 2、相关方案推荐本博已有的 SDI 编解码方案本方案的SDI接收发送本方案的SDI接收图像缩放应用本方案的SDI接收纯verilog图像缩放纯verilog多路视频拼接应用本方案的SDI接收HLS图像缩放HLS多路视频拼接应用本方案的SDI接收HLS多路视频融合叠加应用本方案的S…

工业物联网平台在水务环保、暖通制冷、电力能源等行业的应用

随着科技的不断发展&#xff0c;工业物联网平台作为连接物理世界与数字世界的桥梁&#xff0c;正逐渐成为推动各行业智能化转型的关键力量。在水务环保、暖通制冷、电力能源等行业&#xff0c;工业物联网平台的应用尤为广泛&#xff0c;对于提升运营效率、降低能耗、优化管理等…

mac安装rust开发环境,使用brew安装和全局配置

mac下使用brew可以一键安装环境&#xff1a; brew install rustup 安装完成执行&#xff1a; rustup-init 按照提示配置即可&#xff1a; 出现&#xff1a; 想要全局生效&#xff1a; echo export PATH"$HOME/.cargo/bin:$PATH" >> ~/.bash_profile source…

【Unity+Vuforia】AR 发布安卓的设置

Player Settings > Resolution and Presentation > Default Orientation portrait Player Settings > Other Settings > Auto Graphics API 取消勾选 Player Settings > Other Settings > Graphics APIs 选择OpenGLES3删除其他的 Player Settings…

【elasticsearch实战】从零开始设计全站搜索引擎

业务需求 最近需要一个全站搜索的功能&#xff0c;我们的站点的特点是数据多源&#xff0c;即有我们本地数据库&#xff0c;也包含了第三方数据源&#xff0c;我们的数据类型除了网页&#xff0c;还包括了各种类型的文档&#xff0c;例如&#xff1a;doc、pdf、excel、ppt等格…

MapReduce的原理分析

1.概述 MapReduce的思想核心是“分而治之,先分再合”&#xff0c;适用于大量复杂任务处理场景(大规模数据处理场景)。 MapReduce分两个阶段: map阶段(分)&#xff1a;如果任何可以拆分并且没有依赖&#xff0c;那么就把复杂的任务拆分成小任务&#xff0c;拆分成小任务之后&a…

Ubuntu 安装 KVM 虚拟化

1. Ubuntu 安装 KVM 虚拟化 KVM 是 Linux 内核中一个基于 hypervisor 的虚拟化模块&#xff0c;它允许用户在 Linux 操作系统上创建和管理虚拟机。 如果机器的CPU不支持硬件虚拟化扩展&#xff0c;是无法使用KVM(基于内核的虚拟机)直接创建和运行虚拟机的。此时最多只能使用…

SpringBoot3整合Elasticsearch8.x之全面保姆级教程

整合ES 环境准备 安装配置ES&#xff1a;https://blog.csdn.net/qq_50864152/article/details/136724528安装配置Kibana&#xff1a;https://blog.csdn.net/qq_50864152/article/details/136727707新建项目&#xff1a;新建名为web的SpringBoot3项目 elasticsearch-java 公…

uploads-labs靶场(1-10关)

一、搭建环境: 下载upload-labs源代码 下载链接&#xff1a;https://codeload.github.com/c0ny1/upload-labs/zip/refs/heads/master 将压缩包解压后的文件名改为upload-labs&#xff0c;然后放入phpstudy\www目录下 二、关卡通关: 1、pass-01&#xff08;前端绕过&#xf…

B. Array Fix

思路&#xff1a;我们倒着看&#xff0c;首先判断以下当前元素有没有被操作过&#xff0c;被操作过的话&#xff0c;那么需要改为操作后的数&#xff0c;然后跟当前数的前一个数进行比较&#xff0c;如果a[i] < a[i - 1]的话&#xff0c;那么需要将a[i - 1]拆分&#xff0c;…

【SpringBoot】头条新闻项目实现CRUD登录注册

文章目录 一、头条案例介绍二、技术栈介绍三、前端搭建四、基于SpringBoot搭建项目基础架构4.1 数据库脚本执行4.2 搭建SprintBoot工程4.2.1 导入依赖:4.2.2 编写配置4.2.3 工具类准备 4.3 MybatisX逆向工程 五、后台功能开发5.1 用户模块开发5.1.1 jwt 和 token 介绍5.1.2 jwt…

huawei services HK华为云服务

huaweiserviceshk是一种云计算服务&#xff0c;为华为云服务用户提供了多种服务&#xff0c;包括云服务器、数据库、存储、网络等&#xff0c;用户可以根据自己的需求选择不同的服务并支付相应的费用 如何付费呢&#xff0c;这里可以使用441112&#xff0c;点击获取 卡片信息在…