多线程面试相关知识点

news2024/9/24 4:34:57

文章目录

  • (一) 进程线程和协程的区别
    • 创建线程的4种方式
      • 1. 继承Thread类
      • 2. 实现runnable接口
      • 3. 实现Callable接口
      • 4. 线程池创建
    • runnable 和 callable 有什么区别
    • 线程的 run()和 start()有什么区别?
    • 线程之间的状态变化
    • notify()和 notifyAll()有什么区别?
    • java 中 wait 和 sleep 方法的不同?
  • (二) 线程中并发锁
    • 1)synchronized
      • synchronized关键字的底层原理?
      • synchronized关键字的底层原理-进阶
    • 2) CAS
    • 3) volatile
    • 4) AQS
    • 5) ReentranLock
    • 6) synchronized和Lock的区别
    • 7) ConcurrentHashMap
    • 8) 导致并发出现的根本原因
  • (三) 线程池
    • 1)线程池的核心参数
      • 线程池中有哪些常见的阻塞队列
    • 2)如何确定核心线程数
    • 3) 线程池的种类
  • (四) 对ThreadLocal的理解
      • ThreadLocal基本使用
      • ThreadLocal的实现原理&源码解析

(一) 进程线程和协程的区别

程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。
当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。

一个进程之内可以分为一到多个线程。
一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行。
Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。在 windows中进程是不活动的,只是作为线程的容器

image.png

协程:
协程,英文Coroutines,是一种比线程更加轻量级的存在。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。协程与线程主要区别是它将不再被内核调度,而是交给了程序自己而线程是将自己交给内核调度,所以也不难理解golang中调度器的存在。

定义:协程是轻量级线程。
在一个用户线程上可以跑多个协程,这样就提高了单核的利用率。协程不像进程或者线程,可以让系统负责相关的调度工作,协程是处于一个线程中,系统是无感知的,所以需要在该线程中阻塞某个协程的话,就需要手工进行调度。

总结:
多进程的出现是为了提升CPU的利用率,特别是I/O密集型运算,不管是多核还是单核,开多个进程必然能有效提升CPU的利用率。而多线程则可以共享同一进程地址空间上的资源,为了降低线程创建和销毁的开销,又出现了线程池的概念,最后,为了提升用户线程的最大利用效率,又提出了协程的概念。

创建线程的4种方式

共有四种方式可以创建线程,分别是:继承Thread类、实现runnable接口、实现Callable接口,线程池创建线程。

1. 继承Thread类

public class MyThread extends Thread {
@Override
public void run() {
System.out.println("MyThread...run...");
}
public static void main(String[] args) {
// 创建MyThread对象
MyThread t1 = new MyThread() ;
MyThread t2 = new MyThread() ;
// 调用start方法启动线程
t1.start();
t2.start();
}
}

2. 实现runnable接口

public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable...run...");
}
public static void main(String[] args) {
// 创建MyRunnable对象
MyRunnable mr = new MyRunnable() ;
// 创建Thread对象
Thread t1 = new Thread(mr) ;
Thread t2 = new Thread(mr) ;
// 调用start方法启动线程
t1.start();
t2.start();
}
}

3. 实现Callable接口

public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("MyCallable...call...");
return "OK";
④ 线程池创建线程
}
public static void main(String[] args) throws
ExecutionException, InterruptedException {
// 创建MyCallable对象
MyCallable mc = new MyCallable() ;
// 创建F
FutureTask<String> ft = new FutureTask<String>(mc) ;
// 创建Thread对象
Thread t1 = new Thread(ft) ;
Thread t2 = new Thread(ft) ;
// 调用start方法启动线程
t1.start();
// 调用ft的get方法获取执行结果
String result = ft.get();
// 输出
System.out.println(result);
}

4. 线程池创建

public class MyExecutors implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable...run...");
}
public static void main(String[] args) {
// 创建线程池对象

ExecutorService threadPool =Executors.newFixedThreadPool(3);
threadPool.submit(new MyExecutors()) ;
// 关闭线程池
threadPool.shutdown();
}
}

runnable 和 callable 有什么区别

  1. Runnable 接口run方法没有返回值;Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
  2. Callalbe接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
  3. Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

线程的 run()和 start()有什么区别?

start(): 用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。

run(): 封装了要被线程执行的代码,可以被调用多次。

线程之间的状态变化

image.png

notify()和 notifyAll()有什么区别?

notifyAll:唤醒所有wait的线程
notify:只随机唤醒一个 wait 线程

java 中 wait 和 sleep 方法的不同?

共同点

  • wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
    不同点
  • 方法归属不同
    sleep(long) 是 Thread 的静态方法
    而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
  • 醒来时机不同
    执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
    wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
    它们都可以被打断唤醒
  • 锁特性不同(重点)
    wati方法的调用必须先获取wait对象的锁,而sleep无此限制
    wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃cpu,但你们还可以用)
    而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁

(二) 线程中并发锁

1)synchronized

synchronized关键字的底层原理?

Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有对象锁,其他线程再想获取这个对象锁时,就会阻塞住。

Synchronized的底层由monitor实现的,monitor是jvm级别的对象( C++实现),线程获得锁需要使用对象锁关联monitor。

在使用了synchornized代码块时需要指定一个对象,所以synchornized也被称为对象锁
monitor主要就是跟这个对象产生关联,如下图

image.png

Monitor内部具体的存储结构:

  • Owner:存储当前获取锁的线程的,只能有一个线程可以获取
  • EntryList:关联没有抢到锁的线程,处于Blocked状态的线程
  • WaitSet:关联调用了wait方法的线程,处于Waiting状态的线程
    具体的流程:
  • 代码进入synchorized代码块,先让lock(对象锁)关联的monitor,然后判断Owner是否有线程持有
  • 如果没有线程持有,则让当前线程持有,表示该线程获取锁成功
  • 如果有线程持有,则让当前线程进入entryList进行阻塞,如果Owner持有的线程已经释放了锁,在EntryList中的线程去竞争锁的持有权(非公平)
  • 如果代码块中调用了wait方法,则会进去waitSet中进行等待

synchronized关键字的底层原理-进阶

Monitor实现的锁属于重量级锁,里面涉及到了用户态和内核态的切换、进程的上下文切换,成本较高,性能比较低。
在JDK 1.6引入了两种新型锁机制:偏向锁和轻量级锁,它们的引入是为了解决在没有多线程竞争或基本没有竞争的场景下因使用传统锁机制带来的性能开销问题。

对象的内存结构:
image.png

MarkWord

image.png

2) CAS

CAS的全称是: Compare And Swap(比较再交换),它体现的一种乐观锁的思想,在无锁情况下保证线程操作共享数据的原子性。
在JUC( java.util.concurrent )包下实现的很多类都用到了CAS,

  • AbstractQueuedSynchronizer
  • AtomicXXX

image.png
一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当旧的预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做,并返回false。如果CAS操作失败,则通过自旋的方式等待并再次尝试,知道成功。

CAS 底层实现
CAS 底层依赖于一个 Unsafe 类来直接调用操作系统底层的 CAS 指令

image.png

ReentrantLock中的一段CAS代码
image.png

乐观锁和悲观锁

  • CAS 是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗。
  • synchronized 是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上来锁你们都别想改,我改完了解开锁,你们才有机会

3) volatile

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那
么就具备了两层语义:

  • 保证线程间的可见性
  • 禁止进行指令重排序

4) AQS

全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架,它是构建锁和其他同步组件的基础框架

image.png

AQS常见的实现类

  • ReentrantLock 阻塞式锁
  • Semaphore 信号量
  • CountDownLatch 倒计时锁

工作机制:

image.png

  • 线程0来了以后,去尝试修改state属性,如果发现state属性是0,就修改state状态为1,表示线程0抢锁成功
  • 线程1和线程2也会先尝试修改state属性,发现state的值已经是1了,有其他线程持有锁,它们都会到FIFO队列中进行等待,
  • FIFO是一个双向队列,head属性表示头结点,tail表示尾结点

5) ReentranLock

ReentrantLock翻译过来是可重入锁,相对于synchronized它具备以下特点:

  • 可中断
  • 可以设置超时时间
  • 可以设置公平锁
  • 支持多个条件变量
  • 与synchronized一样,都支持重入

实现原理
ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似

构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。
查看ReentrantLock源码中的构造方法:
提供了两个构造方法,不带参数的默认为非公平

image.png
而Sync的父类是AQS,所以可以得出ReentrantLock底层主要实现就是基于AQS来实现的。

工作流程:
image.png

6) synchronized和Lock的区别

  • 语法层面
    • synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现
    • Lock 是接口,源码由 jdk 提供,用 java 语言实现
    • 使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁
  • 功能层面
    • 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
    • Lock 提供了许多 synchronized 不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量
    • Lock 有适合不同场景的实现,如 ReentrantLock,ReentrantReadWriteLock
  • 性能层面
    • 在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖
    • 在竞争激烈时,Lock 的实现通常会提供更好的性能

7) ConcurrentHashMap

ConcurrentHashMap 是一种线程安全的高效Map集合
底层数据结构:
JDK1.7底层采用分段的数组+链表实现
JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。

image.png

存储流程
image.png

先去计算key的hash值,然后确定segment数组下标
再通过hash值确定hashEntry数组中的下标存储数据
在进行操作数据的之前,会先判断当前segment对应下标位置是否有线程进行
操作,为了线程安全使用的是ReentrantLock进行加锁,如果获取锁是被会使
用cas自旋锁进行尝试

(2) JDK1.8中concurrentHashMap

image.png
在JDK1.8中,放弃了Segment臃肿的设计,数据结构跟HashMap的数据结构是一样的:数组+红黑树+链表
采用 CAS + Synchronized来保证并发安全进行实现CAS控制数组节点的添加
synchronized只锁定当前链表或红黑二叉树的首节点,只要hash不冲突,就不会产生并发问题,效率得到提升

8) 导致并发出现的根本原因

1)原子性

一个线程在CPU中操作不可暂停,也不可中断,要不执行完成,要不不执行

解决方案:
1.synchronized:同步加锁
2.JUC里面的lock:加锁

2)内存可见性

内存可见性:让一个线程对共享变量的修改对另一个线程可见

解决方案:
synchronized
volatile
LOCK

3)有序性

指令重排:处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的

解决方案:
volatile

(三) 线程池

1)线程池的核心参数

image.png

  • corePoolSize 核心线程数目
  • maximumPoolSize 最大线程数目 = (核心线程+救急线程的最大数目)
  • keepAliveTime 生存时间 - 救急线程的生存时间,生存时间内没有新任务,此线程资源会释放
  • unit 时间单位 - 救急线程的生存时间单位,如秒、毫秒等
  • workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
  • threadFactory 线程工厂 - 可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
  • handler 拒绝策略 - 当所有线程都在繁忙,workQueue 也放满时,会触发拒绝策略

工作流程:
image.png

拒绝策略:
1.AbortPolicy:直接抛出异常,默认策略;
2.CallerRunsPolicy:用调用者所在的线程来执行任务;
3.DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4.DiscardPolicy :直接丢弃任务

线程池中有哪些常见的阻塞队列

workQueue - 当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建救急线程执行任务
比较常见的有4个,用的最多是ArrayBlockingQueue和LinkedBlockingQueue
1.ArrayBlockingQueue:基于数组结构的有界阻塞队列,FIFO。
2.LinkedBlockingQueue:基于链表结构的有界阻塞队列,FIFO。
3.DelayedWorkQueue :是一个优先级队列,它可以保证每次出队的任务都是当前队列中执行时间最靠前的
4.SynchronousQueue:不存储元素的阻塞队列,每个插入操作都必须等待一个移出操作。

2)如何确定核心线程数

在设置核心线程数之前,需要先熟悉一些执行线程池执行任务的类型

  • IO密集型任务
    一般来说:文件读写、DB读写、网络请求等
    推荐:核心线程数大小设置为2N+1 (N为计算机的CPU核数)
  • CPU密集型任务
    一般来说:计算型代码、Bitmap转换、Gson转换等
    推荐:核心线程数大小设置为N+1 (N为计算机的CPU核数)

查看CPU核数:
image.png

3) 线程池的种类

  1. 创建使用固定线程数的线程池
    image.png

不建议用Executors创建线程池

image.png

(四) 对ThreadLocal的理解

ThreadLocal是多线程中对于解决线程安全的一个操作类,它会为每个线程都分配一个独立的线程副本从而解决了变量并发访问冲突的问题。ThreadLocal 同时实现了线程内的资源共享

案例:使用JDBC操作数据库时,会将每一个线程的Connection放入各自的ThreadLocal中,从而保证每个线程都在各自的 Connection 上进行数据库的操作,避免A线程关闭了B线程的连接。

image.png

ThreadLocal基本使用

三个主要方法:

  • set(value) 设置值
  • get() 获取值
  • remove() 清除值

ThreadLocal的实现原理&源码解析

ThreadLocal本质来说就是一个线程内部存储类,从而让多个线程只操作自己内部的值,从而实现线程数据隔离

image.png
在ThreadLocal中有一个内部类叫做ThreadLocalMap,类似于HashMap。ThreadLocalMap中有一个属性table数组,这个是真正存储数据的位置

Set方法:
image.png

get方法和Remove方法
image.png

在使用ThreadLocal的时候,强烈建议:务必手动remove

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

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

相关文章

C++之lambda匿名、using、typedef总【全】(二百四十九)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

如何选择最适合 Android 的 SD 卡恢复软件?

所需要的只是心不在焉地点击了错误的按钮、行为不当的应用程序、或者软件或硬件故障。就这样&#xff0c;您的照片消失了&#xff0c;您的笔记无处可寻&#xff0c;您的文件也消失了。 如何选择最适合 Android 的 SD 卡恢复软件 对别人最好的可能对你不起作用&#xff0c;这取…

【Unity小技巧】可靠的相机抖动及如何同时处理多个震动

每篇一句 围在城里的人想逃出来&#xff0c;站在城外的人想冲进去&#xff0c;婚姻也罢&#xff0c;事业也罢&#xff0c;人生的欲望大都如此。——钱钟书《围城》 前言 相机的抖动我相信大家都不陌生&#xff0c;网上其实已经有非常非常多的教程了&#xff0c;之前我也写过…

2.21每日一题(隐函数求导+变上限积分求导)

1、首先 t 0 时&#xff0c;x ? 或者 y ? 求出来 2、等式两边进行一阶求导&#xff0c;把一阶导函数&#xff08;隐函数求导&#xff09;求出来 3、等式两边再次求导&#xff0c;把二阶导函数&#xff08;隐函数求导&#xff09;求出来 注意&#xff1a;隐函数求导及变上…

Java 枚举类型与泛型-第13章

Java 枚举类型与泛型-第13章 1.枚举类型 枚举类型是一种特殊的数据类型&#xff0c;用于表示一组有限的命名常量。枚举类型可以帮助您更清晰地定义和管理相关常量&#xff0c;并提供类型安全性。 1.1使用枚举类型设置常量 枚举类型是一种非常方便的方式来设置常量。我们可以…

YUV空间-两张图片颜色匹配(颜色替换)

在做颜色匹配时&#xff0c;从RGB转换到YUV也有一些优势。因为YUV把亮度和色彩分开了&#xff0c;所以可以更容易地调整图像的亮度分布和色彩平衡⁴。而且&#xff0c;YUV也更接近人类感知颜色的方式&#xff0c;所以可以更好地保持图像的自然感。 这个公式是用来做颜色匹配的&…

Node.js 的适用场景

目录 ​编辑 前言 适用场景 1. 实时应用 用法 代码 理解 2. API 服务器 用法 代码示例 理解 3. 微服务架构 用法 代码示例 理解 总结 前言 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境&#xff0c;它使得 JavaScript 可以脱离浏览器运行在服务器…

如何公网远程访问本地WebSocket服务端

本地websocket服务端暴露至公网访问【cpolar内网穿透】 文章目录 本地websocket服务端暴露至公网访问【cpolar内网穿透】1. Java 服务端demo环境2. 在pom文件引入第三包封装的netty框架maven坐标3. 创建服务端,以接口模式调用,方便外部调用4. 启动服务,出现以下信息表示启动成功…

javaweb+mysql的电子书查阅和下载系统

图书分类查看、热门下载、最新上传、站内数据统计。 登陆注册、图书查询、图书详情、图书下载。 身份分为管理员和用户。 源码下载地址 支持&#xff1a;远程部署/安装/调试、讲解、二次开发/修改/定制

【JavaSE】注释\标识符\关键字\字面常量\数据类型与变量

下面直接介绍Java的基础知识点&#xff0c;很多与C语言相似&#xff0c;但是也有很多不一样的点 目录 一、注释 二、标识符与关键字 1.标识符 2.关键字 三、字面常量 四、数据类型与变量 1.数据类型 2.变量 3.不同类型的变量 4.类型转换与类型提升 5.字符串类型 一…

[减脂期食谱] 自制千岛酱

[减脂期食谱] 自制千岛酱 成品如下&#xff1a; 最中间的那个&#xff0c;算比较居中的颜色吧&#xff0c;其实自己家做原版的千岛酱还是比较简单的&#xff0c;它的底就是蛋黄酱(蛋黄油乳化的酱)&#xff0c;随后里面的材料比较自由&#xff0c;维基百科是这么介绍的&#xf…

『C++成长记』C++入门—— 函数重载引用

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;C &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、函数重载 &#x1f4d2;1.1函数重载的概念 &#x1f4d2;1.2函数重载的种类 …

【密评】商用密码应用安全性评估从业人员考核题库(十五)

商用密码应用安全性评估从业人员考核题库&#xff08;十五&#xff09; 国密局给的参考题库5000道只是基础题&#xff0c;后续更新完5000还会继续更其他高质量题库&#xff0c;持续学习&#xff0c;共同进步。 3501 单项选择题 根据GM/T 0115 《信息系统密码应用测评要求》&am…

基于人机环境系统的新全局工作空间理论

&#xff08;旧的&#xff09;全局工作空间理论是由心理学家伯纳德巴尔斯&#xff08;Bernard Baars&#xff09;提出的一种关于意识的理论。该理论认为&#xff0c;意识是一种全局性的心理状态&#xff0c;其中包含了我们当前的感知、思维和意识内容。根据全局工作空间理论&am…

【Python从入门到进阶】40、requests的基本使用

接上篇《39、使用Selenium自动验证滑块登录》 上一篇我们介绍了使用selenium进行滑块自动验证操作。本篇我们结束selenium的章节&#xff0c;来学习requests库的基本使用。 一、requests与urllib的爱恨情仇 1、requests与urllib的区别 大家在前面的学习中&#xff0c;访问网…

2.23每日一题(反常积分收敛性的判断)

解法一&#xff1a;用定义&#xff08;当被积函数的原函数比较好找时&#xff09;&#xff1a; 积分结果为存在则收敛&#xff0c;不存在则发散。 解法二&#xff1a;通过p积分的比较法判断敛散性&#xff1a; 即被积函数与p积分相比较&#xff0c;使得两者同敛散&#xff1b;再…

轻量封装WebGPU渲染系统示例<3>-纹理立方体(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/version-1.01/src/voxgpu/sample/ImgTexturedCube.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 2. 高频调用与低频调用隔离。 3. 面向用户的易用性封装。 4. 渲染数据和渲染机制分离。 5…

算法通过村第十七关-贪心|黄金笔记|跳跃游戏

文章目录 前言跳跃游戏最短跳跃游戏总结 前言 提示&#xff1a;曾走过山&#xff0c;走过水&#xff0c;其实只是借助他们走过我的生命&#xff1b;我看着天&#xff0c;看着地&#xff0c;其实只是借助它们确定我的位置&#xff1b;我爱这她&#xff0c;爱着你&#xff0c;其实…

【Java】基于微服务架构的智慧工地监管云平台源码带APP

前言&#xff1a;智慧工地监管平台是一种利用物联网、云计算、大数据等技术手段实现工地信息化管理的解决方案。它通过数据采集、分析和应用&#xff0c;在实时监控、风险预警、资源调度等方面为工地管理者提供了全方位的支持&#xff0c;提高了工地管理的效率和质量。智慧监管…

数据结构OJ题

目录 1.字符串左旋 2.字符串旋转结果 3.旋转数组 4.移除元素 本篇主要是讲解一些OJ题目。 1.字符串左旋 字符串左旋 实现一个函数&#xff0c;可以左旋字符串中的k个字符 例如&#xff1a; ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 方法1【暴力求解】 翻转1…