当前位置: 首页 > news >正文

面试题分享之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博客

相关文章:

面试题分享之Java并发篇

注意:文章若有错误的地方,欢迎评论区里面指正 🍭 系列文章目录 面试题分享之Java集合篇(三) 面试题分享之Java集合篇(二) 面试题分享之Java基础篇(三) 前言 今天给小…...

bpmn-js 多实例配置MultiInstanceLoopCharacteristics实现或签会签

使用bpmn-js流程图开发过程中会遇到会签和或签的问题,这个时候我们就需要使用多实例配置来实现BPMN 2.0的配置实现了,多实例任务,是从流程编辑概念之初也就是Activiti时期就存在的一个方式。所谓的多实例任务也就是字面意思,一个任务由多个人完成,常见于我们的审批流程的或…...

【gpedit.msc】组策略编辑器的安装,针对windows家庭版,没有此功能

创建一个记事本文件然后放入以下内容 echo offpushd "%~dp0"dir /b %systemroot%\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >gp.txtdir /b %systemroot%\servicing\Packages\Microsoft-Windows-GroupPolicy-…...

带EXCEL附件邮件发送相关代码

1.查看生成的邮件 2.1 非面向对象的方式(demo直接copy即可) ​ REPORT Z12. DATA: IT_DOCUMENT_DATA TYPE SODOCCHGI1,IT_CONTENT_TEXT TYPE STANDARD TABLE OF SOLISTI1 WITH HEADER LINE,IT_PACKING_LIST TYPE TABLE OF SOPCKLSTI1 WITH HEADER LIN…...

【算法作业】均分卡牌,购买股票

问题描述 John 有两个孩子,在 John病逝后,留下了一组价值不一定相同的魔卡, 现在要求你设计一种策略,帮John的经管人将John的这些遗产分给他的两个孩子,使得他们获得的遗产差异最小(每张魔卡不能分拆&#…...

python作业

题目 分析 步骤: 判断先画空格还是数字 当有n层时,第i层有多少个空格第i层的起始数字是几,结尾是几,即数字取值范围当有n层时,第i层有多少个数字 代码 模式A n int(input("请输入行数:")) for i in range(…...

【Linux的文件篇章 - 管道文件】

Linux学习笔记---013 Linux的管道文件1、进程间通信1.1、进程为什么要通信?1.2、进程如何通信?1.3、进程通信的方式? 2、匿名管道2.1、理解一种现象2.2、基本概念和管道原理 3、管道的使用3.1、代码样例3.2、如何使用管道通信呢?3…...

C# 局部静态函数,封闭方法中的最佳选择

C# 局部静态函数,封闭方法中的最佳选择 简介特性 应用场景辅助计算递归与尾递归优化筛选与过滤操作查找与映射操作 生命周期静态局部函数 vs 普通局部函数性能封装性可读性 简介 C# 局部静态函数(Local Static Functions)是一种函数作用域内…...

【MySQL】MySQL 8.4.0 长期支持版(LTS)安装

就在2024年 “5.1” 节前,MySQL官方发布了8.4.0长期支持版(LTS - Long Term Support)。根据官方提供的文档,在本地虚拟机进行安装测试。 安装、配置和启动过程记录如下: 第一步,上传到安装包(my…...

nest中的ORM

在 Nest.js 中执行 SQL 查询通常涉及使用 TypeORM 或 Sequelize 这样的 ORM(对象-关系映射)库。这些库使得在 Nest.js 应用程序中连接和操作 SQL 数据库变得更加简单和直观。 以下是一个使用 TypeORM 在 Nest.js 中执行 SQL 查询的示例代码:…...

TCP(Transmission Control Protocol,传输控制协议)如何保证数据的完整性?

TCP(Transmission Control Protocol,传输控制协议)通过一系列机制来保证数据传输的可靠性和无错性,这些机制主要包括: 校验和:TCP报文段包含一个校验和字段,用于检测数据在传输过程中是否出错。…...

Numpy库介绍

NumPy(Numerical Python的缩写)是Python中用于科学计算的一个强大的库。它提供了高性能的多维数组对象(即ndarray)、用于处理这些数组的工具以及用于数学函数操作的函数。让我为你介绍一下它的一些主要功能: 1. 多维数…...

临时有事无法及时签字盖章?试试用契约锁设置“代理人”

遇到“领导休假中、在开重要会议、外出考察或者主任医生手术中等”一段时间内不方便或者无法及时签字盖章的情况怎么办?业务推进不了只能干等? 契约锁电子签及印控平台支持印章、签名“临时授权”、“代理签署”,实现指定人、指定时间段、指定…...

数据库权限管理

1.查看系统级权限(global level) Select * from mysql.user\G; 2.查看数据库中所有表的权限 Select * from mysql.db\G 3.远程连接数据库 第一步在有数据库服务上的主机上:授权 grant all on *.* to root192.168.40.83 identified by Zxy20234; 第…...

如何创建一个 Django 应用并连接到数据库

简介 Django 是一个用 Python 编写的免费开源的 Web 框架。这个工具支持可扩展性、可重用性和快速开发。 在本教程中,您将学习如何为一个博客网站建立与 MySQL 数据库的初始基础。这将涉及使用 django-admin 创建博客 Web 应用程序的骨架结构,创建 MyS…...

【算法刷题day44】Leetcode:518. 零钱兑换 II、377. 组合总和 Ⅳ

文章目录 Leetcode 518. 零钱兑换 II解题思路代码总结 Leetcode 377. 组合总和 Ⅳ解题思路代码总结 草稿图网站 java的Deque Leetcode 518. 零钱兑换 II 题目:518. 零钱兑换 II 解析:代码随想录解析 解题思路 先遍历物品,再遍历背包。 代码…...

『51单片机』AT24C02[IIC总线]

存储器的介绍 ⒈ROM的功能⇢ROM的数据在程序运行的时候是不容改变的,除非你再次烧写程序,他就会改变,就像我们的书本,印上去就改不了了,除非再次印刷,这个就是ROM的原理。 注→在后面发展的ROM是可以可写可…...

Jenkins与Rancher的配合使用

Jenkins和Rancher是两个常用的DevOps工具,可以很好地配合使用来实现持续集成和持续部署。 Jenkins是一个开源的自动化构建工具,可以实现自动化的代码构建、测试和部署等一系列操作。可以通过Jenkins来触发构建任务,例如从代码仓库中拉取最新的…...

GIS入门,常用的多边形平滑曲线算法介绍和JavaScript的多边形平滑曲线算法库chaikin-smooth的实现原理和使用

前言 本章介绍一下常用的多边形平滑曲线算法及其使用案例。 多边形平滑算法通常用于图形处理或计算机图形学中,以使线条或曲线在连接处平滑过渡,而不出现明显的棱角或断裂。多边形平滑算法有多种实现方法,其中一些常见的有下面几种: 贝塞尔曲线插值(Bezier Curve Interpo…...

气膜体育馆内部的采光效果如何?—轻空间

气膜体育馆内部的采光效果如何?这是许多人对这种创新建筑的一个关键关注点。 首先,气膜体育馆的采光性非常好。阳光透过屋顶时以漫射光的方式进入室内,这种透射方式使得室内的光线柔和而均匀。从内部观察,整个屋顶就像一个连续的明…...

XML Group端口详解

在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

XCTF-web-easyupload

试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...

centos 7 部署awstats 网站访问检测

一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

Selenium常用函数介绍

目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...