面试题分享之Java并发篇

注意:文章若有错误的地方,欢迎评论区里面指正 🍭 

系列文章目录

  • 面试题分享之Java集合篇(三)

  • 面试题分享之Java集合篇(二)

  • 面试题分享之Java基础篇(三)

前言

        今天给小伙伴们分享我整理的关于Java并发的一些常见面试题,这期涉及到线程的一些知识,所以要求小伙伴有一些操作系统的知识,不清楚也不要紧,也不是什么很难的知识点。🌈


一、什么是线程?什么是进程?

  • 线程:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 进程:进程是操作系统分配资源的基本单位,它是程序在计算机上的一次执行活动。当系统为一个程序分配资源后,该程序就成为一个独立的进程。

👨‍💻面试官追问线程跟进程的区别是什么?

  1. 线程是进程划分成的更小的运行单位。
  2. 独立性:进程是独立的,拥有独立的内存空间和系统资源;而线程是依赖进程的,多个线程共享其所属进程的内存空间和资源。
  3. 开销:进程的创建和销毁开销较大,因为需要为其分配和回收系统资源;而线程的创建和销毁开销较小。
  4. 切换速度:由于线程的上下文信息相对较少,因此线程间的切换速度通常比进程间的切换速度快。
  5. 通信与数据共享:进程间的通信和数据共享相对困难,需要通过特定的机制来实现;而线程间的通信和数据共享相对容易,因为它们共享其所属进程的内存空间和资源。
  6. 并发性:进程和线程都可以实现并发执行,但线程通常用于实现更细粒度的并发操作。在一个多核或多处理器的系统中,多个进程可以并行执行;而在一个进程中,多个线程也可以并行执行(如果处理器支持多线程)。

二、说一下线程的生命周期,它有几种状态

线程的生命周期包含五个阶段,即五种状态,分别是:

  1. 新建状态(New):新创建了一个线程对象,但还没有调用start()方法。在这个阶段,线程只是被分配了必要的资源,并初始化其状态。

  2. 就绪状态(Runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。换句话说,线程已经做好了执行的就绪准备,表示可以运行了,但还不是正在运行的线程。

  3. 运行状态(Running):当就绪的线程被调度并获得CPU资源时,便进入运行状态,开始执行run()方法的线程执行体。在这个阶段,线程正在执行其任务。

  4. 阻塞状态(Blocked):在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态。阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。阻塞的情况可能包括:

    • 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。
    • 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
    • 阻塞于锁:线程试图获取某个锁,但该锁当前被其他线程持有。

    直到线程进入就绪状态,才有机会转到运行状态。

  5. 死亡状态(Dead):当线程退出run()方法时,线程就会自然死亡,处于终止或死亡状态,也就结束了生命周期。

这五个状态构成了线程从创建到消亡的完整生命周期。

三、说一下你对守护线程的了解?

守护线程Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出,因此,在守护线程中执行涉及I/O操作的任务可能会导致数据丢失或其他不可预测的问题。可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。

👨‍💻面试官追问如何使用守护线程,使用时有什么要注意的

使用方法

  1. 创建线程:首先,你需要创建一个继承自Thread类的新线程或者实现Runnable接口的对象。

  2. 设置守护线程:在调用start()方法之前,通过调用线程的setDaemon(true)方法将其设置为守护线程。

  3. 启动线程:调用线程的start()方法启动线程。

示例

public class DaemonThreadExample extends Thread{public DaemonThreadExample() {// 默认构造函数}@Overridepublic void run() {while (true) {// 守护线程执行的代码System.out.println("守护线程正在运行....");try {Thread.sleep(1000); // 暂停一秒} catch (InterruptedException e) {e.printStackTrace();// 如果守护线程被中断,则退出循环break;}}}public static void main(String[] args) {// 创建守护线程对象DaemonThreadExample daemonThread = new DaemonThreadExample();// 设置为守护线程daemonThread.setDaemon(true);// 启动守护线程daemonThread.start();// 主线程执行其他任务,例如休眠一段时间try {Thread.sleep(5000); // 主线程休眠5秒} catch (InterruptedException e) {e.printStackTrace();}// 当主线程结束时,守护线程也会立即停止System.out.println("当主线程结束时,守护线程停止.");}
}

注意事项

  1. 设置守护线程的时机:必须在调用线程的start()方法之前调用setDaemon(true)方法将其设置为守护线程。如果在调用start()方法之后调用setDaemon(true),则会抛出IllegalThreadStateException

  2. 守护线程与前台线程:守护线程主要是为前台线程服务的。当所有的前台线程都结束时,JVM会立即停止,此时守护线程也会被强制终止。因此,守护线程不应该执行任何重要的或必须完成的任务。

  3. 避免在守护线程中执行I/O操作:由于守护线程的生命周期是不确定的,可能在任何时候被终止,因此在守护线程中执行I/O操作可能会导致数据丢失或文件损坏等问题。

  4. 线程池中的守护线程:如果你在使用线程池(如ExecutorService),并希望线程池中的线程是守护线程,那么你需要确保在调用Executors的工厂方法创建线程池时,传入的线程工厂(ThreadFactory)创建的线程是守护线程。但是,Java的ExecutorService默认并不支持直接设置守护线程,因为线程池通常用于执行重要的后台任务,这些任务应该由前台线程来执行。

  5. 不要依赖守护线程完成关键任务:由于守护线程的生命周期受前台线程的控制,因此不应该依赖守护线程来完成关键任务或需要持久运行的任务。这些任务应该由前台线程来执行。

守护线程在Java编程中有多种应用场景,这些场景通常涉及需要在后台运行的任务,以支持其他线程或执行特定的服务,比如:日志记录、定时任务、数据统计、垃圾回收等。

给大家写一个日志记录的场景:

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class LogRecorder {private final ScheduledExecutorService scheduler;private final BufferedWriter logWriter;public LogRecorder(String logFilePath) throws IOException {// 创建一个单线程的守护线程池scheduler = Executors.newSingleThreadScheduledExecutor(r -> {Thread thread = new Thread(r);// 设置为守护线程thread.setDaemon(true);return thread;});// 初始化日志文件的写入器logWriter = new BufferedWriter(new FileWriter(logFilePath, true));}// 启动日志记录任务public void startLogging() {// 每隔一段时间记录一条日志(这里假设为每5秒)scheduler.scheduleAtFixedRate(() -> {try {// 模拟生成一条日志String logMessage = "日志信息: " + System.currentTimeMillis();logWriter.write(logMessage);logWriter.newLine();logWriter.flush();System.out.println(logMessage);} catch (IOException e) {e.printStackTrace();// 可以在这里处理异常,例如重新打开文件或记录错误日志}}, 0, 5, TimeUnit.SECONDS);}// 停止日志记录任务并关闭文件写入器public void stopLogging() throws IOException {scheduler.shutdown(); // 停止任务调度logWriter.close(); // 关闭文件写入器}public static void main(String[] args) throws IOException {// 假设日志文件路径为"logs/application.log"String logFilePath = "文件地址/xxx.log";LogRecorder logRecorder = new LogRecorder(logFilePath);// 启动日志记录任务logRecorder.startLogging();// 模拟主线程执行一些任务try {Thread.sleep(30000); // 主线程休眠30秒} catch (InterruptedException e) {e.printStackTrace();}// 停止日志记录任务并关闭文件写入器logRecorder.stopLogging();// 主线程结束,由于守护线程的存在,JVM不会立即关闭// 但由于我们调用了scheduler.shutdown(),守护线程中的任务将不再执行System.out.println(" 主线程结束,停止写入日志.");}
}

四、使用多线程可能带来什么问题

在进行并发编程时,如果希望通过多线程执行任务让程序运行得更快,会面临非常多的挑战,比如:

  • 上下文切换的问题:频繁的上下文切换会影响多线程的执行速度。
  • 死锁的问题
  • 受限于硬件和软件的资源限制问题:在进行并发编程时,程序的执行速度受限于计算机的硬件或软件资源。

👨‍💻面试官追问既然你提到了锁,那么死锁产生的必要条件是什么?

  • 互斥条件(Mutual Exclusion):至少有一个资源必须处于非共享状态,即一次只能被一个进程或线程占用。
  • 请求与保持条件(Hold and Wait):进程或线程至少需要持有一个资源,并且在等待其他资源时不释放已占有的资源。
  • 不可剥夺条件(No Preemption):已分配给进程或线程的资源不能被强制性地剥夺,只能由持有资源的进程或线程主动释放。
  • 循环等待条件(Circular Wait):存在一个进程或线程的资源申请序列,使得每个进程或线程都在等待下一个进程或线程所持有的资源。

👨‍💻面试官继续追问那你说说Java多线程避免死锁有什么办法?

  • 按顺序获取锁:当多个线程需要获取多个锁时,为了避免死锁,可以约定一个获取锁的顺序,并且所有线程都按照这个顺序来获取锁。这样可以确保锁是以一致的顺序被请求和释放的。
  • 避免嵌套锁:尽量减少锁的嵌套使用,避免在持有锁的情况下再申请其他锁。如果必须使用多个锁,尽量保证锁的获取顺序一致,以避免死锁。
  • 使用定时锁或tryLock():Java提供了定时锁的机制,即在尝试获取锁的时候设定一个等待的时间。如果在这个时间内未能获取到锁,就主动放弃。另外,可以使用tryLock()方法来尝试获取锁,如果获取失败则不会阻塞,可以继续执行其他逻辑或等待一段时间后重新尝试。
  • 使用并发工具类:Java中的并发工具类,如java.util.concurrent包下的类,提供了许多高级并发工具,如SemaphoreCountDownLatchCyclicBarrier等,这些工具可以帮助简化多线程编程,并减少死锁的风险。
  • 避免线程持有锁的时间过长:当一个线程持有一个锁并长时间不释放时,会阻塞其他线程的访问,并增加死锁的概率。因此,需要尽量缩短线程持有锁的时间,及时释放锁,以便其他线程能够及时获取锁并继续工作。
  • 仔细设计资源申请顺序:在设计多线程程序时,要仔细考虑资源申请的顺序。如果多个线程都需要获取同一组资源,可以考虑引入一个资源分配器,通过分配器来按照一定的策略来分配资源,避免资源的竞争。
  • 死锁检测和恢复:虽然预防死锁是最好的策略,但有时死锁仍然可能发生。在这种情况下,可以使用死锁检测算法来及时发现死锁,并采取必要的措施进行恢复,如终止一个或多个进程或线程,或者回滚到某个一致的状态。

👨‍💻面试官继续追问:你能写一个Java死锁的案例吗?

当两个或多个线程无限期地等待一个资源,而这些资源又被其他线程持有时,就会发生死锁。

public class DeadlockExample {
/*
这个死锁大概思路:
1、线程1拿到lock1休眠5s
2、线程1休眠后,线程2拿到lock2
3、线程1休眠结束后。尝试拿lock2,但是lock2被线程2占有
4、同理,线程2休眠结束后,尝试拿lock1,但是lock1又被线程1占有
因此,造成了死锁
*/public static void main(String[] args) {Object lock1 = new Object();Object lock2 = new Object();new Thread(() -> {synchronized (lock1){System.out.println(Thread.currentThread().getName()+"已经获得a锁");try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"睡眠5ms结束");synchronized (lock2) {System.out.println(Thread.currentThread().getName()+"已经获得b锁");}}},"线程1").start();new Thread(() -> {synchronized (lock2) {System.out.println(Thread.currentThread().getName() + "已经获得b锁");try {Thread.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"睡眠5ms结束");synchronized (lock1) {System.out.println(Thread.currentThread().getName() + "已经获得a锁");}}},"线程2").start();}
}

五、说一说sleep()、wait()、join()、yield()的区别

在说这几个方法区别之前,先给大家说一下什么锁池等待池

1.锁池

所有需要竞争同步锁的线程都会放在锁池中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。

2.等待池

当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用notify()notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所以线程放到锁池当中。

sleep跟wait的区别

  1. sleep方法是Thread类的静态方法,wait是Object类的本地方法
  2. sleep方法不会释放锁,但是wait会释放锁,而且会加入到等待队列中

sleep就是把cpu执行资格执行权释放出去,不在运行此线程,当定时时间结束后再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了。而如果sleep时该线程有锁,那么sleep也不会释放这个,而是把锁带着进入了冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这把锁。也就是无法执行程序。如果在睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出interruptexception异常返回,这和wait是一样的。

        3.sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字。

        4.sleep不需要被唤醒,但是wait需要(不指定时间需要被别人中断)。

        5.sleep一般用于当前线程休眠,或者轮询暂停操作,wait则多用于多线程之间的通信。

        6.sleep会让出CPU执行时间并且强制上下文切换,而wait不一定,wait后还是有机会重新争夺锁继续执行的。

 yield跟join的区别

yield()执行后线程直接进入就绪状态,马上释放cpu的执行权,但是依旧保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行

join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那么线程B会进入到阻塞队列,直到线程A结束或中断线程

给大家举一个简单的例子:t1线程睡4秒,然后执行,之后又调用了join()使主线程进入阻塞,直到t1线程执行完之后主线程才会执行。(注意:是主线程进入阻塞而不是t1阻塞

public static void main(String[] args) throws InterruptedException {Thread t1 =  new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(4000);}catch (InterruptedException e){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行了。。。");}});t1.start();t1.join();System.out.println(Thread.currentThread().getName()+"执行了。。。");}/*打印结果:
Thread-0执行了。。。 (先执行)
main执行了。。。 (后执行)
*/

六、知道线程中的 run() 和 start() 有什么区别吗?

  1. 功能

    • run(): 这是Thread类中的一个方法,用于定义线程要执行的任务。当你直接调用一个线程的run()方法时(例如myThread.run()),它会在当前线程(通常是主线程)中执行,而不是在新的线程中。这意味着它不会启动一个新线程。
    • start(): 这是Thread类中的另一个方法,用于启动一个新线程来执行run()方法中的代码。当你调用start()方法时(例如myThread.start()),Java会创建一个新的线程,并在该线程中调用run()方法。这意味着run()方法中的代码会在新的线程中执行。
  2. 执行上下文

    • 直接调用run():代码在当前线程(通常是主线程)的上下文中执行。
    • 调用start():Java会创建一个新的线程,并在该线程的上下文中执行run()方法中的代码。
  3. 返回值

    • run(): 它没有返回值(即返回类型为void)。
    • start(): 它也没有返回值(返回类型为void),但它启动了一个新线程。
  4. 异常处理

    • 如果你在run()方法中抛出一个未检查的异常(例如RuntimeException),并且你没有在该方法中捕获它,那么它会在当前线程中直接抛出,并且可能会导致程序崩溃(除非有其他地方的代码捕获了该异常)。
    • 如果你在start()方法中抛出一个异常,那么它实际上是在调用start()的线程中抛出的,而不是在新创建的线程中。这是因为start()方法是在当前线程中调用的,而新线程是在start()方法内部创建的。
  5. 线程状态

    • 当线程首次被创建时,它的状态是NEW
    • 当你调用start()方法时,线程的状态变为RUNNABLE(或BLOCKEDWAITINGTIMED_WAITING等,具体取决于线程的行为)。
    • 如果你直接调用run()方法而不是start()方法,线程将不会被创建为单独的线程,并且它的状态仍然是NEW(尽管这在实际中并不常见,因为通常你会在创建线程后立即调用start())。

总结:你应该总是使用start()方法来启动一个新线程,而不是直接调用run()方法。

七、说了这么多,Java程序中如何保证多线程的安全

  • 原子性:在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么都执行,要么都不执行。可以用Java提供了java.util.concurrent.atomic包下的原子类,如AtomicIntegerAtomicLong等,这些类中的方法都是线程安全,或者java.util.concurrent.locks 包下的 Lock 接口提供了比 synchronized 更灵活的锁机制,包括可重入锁、读写锁、定时锁
  • 可见性:Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值

另外,通过synchronizedLock也能够保证可见性,synchronizedLock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

  • 有序性:在Java里面,可以通过volatile关键字来保证一定的“有序性”。另外可以通过synchronized和Lock来保证有序性,很显然,synchronized和Lock保证每个时刻是只有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

总结

这期的面试题需要大家多理解多记多背,先理解在背。好了,今天的分享就到这,喜欢的小伙伴记得三连欧😘

参考文章:并发编程&JVM_ΘLLΘ的博客-CSDN博客

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

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

相关文章

Meta Llama 3 使用 Hugging Face 和 PyTorch 优化 CPU 推理

原文地址:meta-llama-3-optimized-cpu-inference-with-hugging-face-and-pytorch 了解在 CPU 上部署 Meta* Llama 3 时如何减少模型延迟 2024 年 4 月 19 日 万众期待的 Meta 第三代 Llama 发布了,我想确保你知道如何以最佳方式部署这个最先进的&…

时间日志格式的统一和定制

返回当前格式的时间没有错误&#xff0c;但是不符合中国人的阅读习惯 解决&#xff1a; 方案一&#xff1a;JsonFormat 解决后端 传到 前端格式问题 依赖&#xff1a; <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jack…

STM32:GPIO输出

文章目录 1、GPIO介绍1.1 GPIO的基本结构1.1 GPIO的位结构 2、 GPIO工作模式3、GPIO标准外设库接口函数3.1 RCC接口函数3.2 GPIO接口函数3.2.1 GPIO的读取函数3.2.1 GPIO的写入函数 4、GPIO的初始化 1、GPIO介绍 GPIO&#xff08;General Purpose Input Output&#xff09;通用…

Python设计模式 - 单例模式

定义 单例模式是一种创建型设计模式&#xff0c; 其主要目的是确保一个类只有一个实例&#xff0c; 并提供一个全局访问点来访问该实例。 结构 应用场景 资源管理&#xff1a;当需要共享某个资源时&#xff0c;例如数据库连接、线程池、日志对象等&#xff0c;可以使用单例模…

铜川市各区县高新技术企业奖励补贴政策文件,铜川高企申报条件

铜川市高新技术企业奖励补贴政策文件解读&#xff1a;《铜川市科技型企业创新发展倍增计划》 【高企申请 找 小编 见 个 人 简 介 】一、指导思想 按照市委、市政府部署要求&#xff0c;以强化科技型企业创新主体地位为牵引&#xff0c;以培育壮大主体规模、提升…

【UnityRPG游戏制作】Unity_RPG项目_玩法相关

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…

【算法与数据结构】哈希表

文章目录 引入哈希函数介绍便利店的例子Python3 中的哈希表C 中的哈希表 应用将散列表用于查找防止重复将散列表用作缓存 哈希冲突与解决链地址法开放寻址 总结参考资料写在最后 引入 假设你在一家便利店上班&#xff0c;你不熟悉每种商品的价格&#xff0c;在顾客需要买单是时…

详述DM9161芯片的特性和用法

目录 概述 1. 认识DM9161 2 DM9161的特性 2.1 特性总结 2.2 结构框图 3 功能描述 4 RMII接口 4.1 100Base-TX Operation 4.2 10Base-T Operation 4.3 Auto-Negotiation 4.4 HP Auto-MDIX功能描述 6 DM9161的寄存器 6.1 寄存器列表 6.2 寄存器功能介绍 6.2.1 基本…

ubuntu20中ros与anaconda的python版本冲突问题

系统环境 原本系统是ubuntu20 noetic&#xff0c;python都在/usr/bin中&#xff0c;一共是两个版本的python&#xff0c;一个是python3.8&#xff0c;另一个是python2.7。 问题发现 当安装anaconda后&#xff0c;并且将anaconda的bin目录加入到系统环境中时候&#xff0c;…

Stable Diffusion webUI 配置指南

Stable Diffusion webUI 配置指南 本博客主要介绍部署Stable Diffusion到本地&#xff0c;生成想要的风格图片。 文章目录 Stable Diffusion webUI 配置指南1、配置环境&#xff08;1&#xff09;pip环境[可选]&#xff08;2&#xff09;conda环境[可选] 2、配置Stable Diffu…

Monorepo(单体仓库)与MultiRepo(多仓库): Monorepo 单体仓库开发策略与实践指南

&#x1f31f; 引言 在软件开发的浩瀚宇宙里&#xff0c;选择合适的代码管理方式是构建高效开发环境的关键一步。今天&#xff0c;我们将深入探讨两大策略——Monorepo&#xff08;单体仓库&#xff09;与MultiRepo&#xff08;多仓库&#xff09;&#xff0c;并通过使用现代化…

CMakeLists.txt语法规则:部分常用命令说明一

一. 简介 前一篇文章简单介绍了CMakeLists.txt 简单的语法。文章如下&#xff1a; CMakeLists.txt 简单的语法介绍-CSDN博客 接下来对 CMakeLists.txt语法规则进行具体的学习。本文具体学习 CMakeLists.txt语法规则中常用的命令。 二. CMakeLists.txt语法规则&#xff1a;…

【Qt问题】VS2019 Qt win32项目如何添加x64编译方式

解决办法&#xff1a; 注意改为x64版本以后&#xff0c;要记得在项目属性里&#xff0c;修改Qt Settings、对应的链接include、lib等 参考文章 VS2019 Qt win32项目如何添加x64编译方式_vs2019没有x64-CSDN博客 有用的知识又增加了~

Spring事件

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Spring⛺️稳中求进&#xff0c;晒太阳 Spring事件 简洁 Spring Event&#xff08;Application Event&#xff09;就是一个观察者模式&#xff0c;一个bean处理完任务后希望通知其他Bean的…

OpenCV人脸识别C++代码实现Demo

OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉库&#xff0c;它提供了很多函数&#xff0c;这些函数非常高效地实现了计算机视觉算法。 官网&#xff1a;https://opencv.org/ Github: https://github.com/opencv/opencv Gitcode…

微博一级评论爬虫

cookies需要替换成自己的 import requests import requests from lxml import etree import openpyxl from concurrent.futures.thread import ThreadPoolExecutor import re from datetime import datetime, timedelta from urllib import parse from jsonpath import jsonpa…

查找算法与排序算法

查找算法 二分查找 (要求熟练) // C// 二分查找法&#xff08;递归实现&#xff09; int binarySearch(int *nums, int target, int left, int right) // left代表左边界&#xff0c;right代表右边界 {if (left > right) return -1; // 如果左边大于右边&#xff0c;那么…

初始化Linux或者Mac下Docker运行环境

文章目录 1 Mac下安装Docker2 Linux下安装Docker2.1 确定Linux版本2.2 安装Docker2.3 配置加速镜像 3 Docker安装校验4 安装docker-compose4.1 直接下载二进制文件4.2 移动二进制文件到系统路径4.3 设置可执行权限4.4 验证安装 1 Mac下安装Docker mac 安装 docker 还是比较方便…

open3d 处理las点云数据

laspy读取las点云数据 转换格式 open3d 处理:法向量估计 分享给有需要的人,代码质量勿喷。 import numpy as np import os import math import laspy import open3d as o3d# 输入文件夹路径 dirInput = "F://data"# 要筛选的文件后缀 extension = ".las&q…

配置Zephyr编译环境

安装chocolatey 以管理员身份运行PowerShell&#xff0c;然后在PowerShell下执行以下命令&#xff0c;安装chocolatey。 Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol [System.Net.ServicePointManager]::Securi…