Java多线程面试题

目录

一.线程和进程的区别

二.保证线程安全的手段

三.创建多线程的方式

四.线程池的讲解            

五.volatile和synchronzied的区别

 六.synchronized 和 Lock的区别

七.产生死锁的条件

八.Java当中常见的锁策略


本专栏全是博主自己收集的面试题,仅可参考,不能相信面试官就出这种题目。

一.线程和进程的区别

        线程是轻量级进程,它不会像进程一样,创建的时候需要大量的资源(PCB,硬盘资源),而也是因为不需要大量资源,它的速度很快很快。

为什么不需要创建大量资源呢?因为它是共享的,先创建一个进程,就具备了:

  1. 内存空间: 新进程分配一个地址空间,这个地址空间包含了进程的代码段、数据段和堆栈空间。

  2. 进程控制块(PCB): 每个进程都需要一个 PCB 来存储和管理进程的信息,包括进程状态、程序计数器(PC)、堆栈指针、内存分配信息、文件描述符、进程优先级等。PCB 是操作系统用来管理和调度进程的重要数据结构。

  3. 程序和数据: 新进程需要加载和执行的程序文件,以及程序执行所需的数据。这些通常是通过文件系统中的可执行文件和数据文件来提供的。

  4. 上下文切换: 创建进程时,操作系统需要进行上下文切换,保存当前进程的状态,并将新进程的状态加载到合适的数据结构(如 PCB 和 CPU 寄存器)中,以便新进程能够开始执行。

  5. 文件描述符和 I/O 资源: 如果新进程需要访问文件或其他 I/O 设备,操作系统需要为其分配文件描述符,并确保进程有足够的权限和资源来访问这些设备。

  6. 权限和安全上下文: 创建进程时,操作系统还需要确保新进程在安全上下文中运行,即进程有足够的权限执行其所需的操作,如访问特定的文件或调用特定的系统服务。

而在其中,线程共享的资源是:内存空间、文件描述符、进程上下文和堆内存。

线程的私有资源是:线程栈、寄存器集合、线程ID以及线程状态。

二.保证线程安全的手段

        了解线程安全的手段前,我们需要了解一下,为什么线程不安全:多线程环境下,当多个线程同时访问和修改共享资源时,没有采取合适的同步措施,可能导致数据不一致、程序崩溃或不可预测的行为

  1. 竞态条件(Race Condition): 竞态条件发生在多个线程同时访问某个共享资源,并且对该资源的访问顺序决定了最终的执行结果。如果没有正确同步,可能会导致意外的结果或数据损坏。

  2. 数据竞争(Data Race): 数据竞争是指至少两个线程并发访问同一内存位置,并且至少其中一个是写操作。如果没有适当的同步机制(如互斥锁或原子操作),则读取到的数据可能是不一致的或无效的。

  3. 非原子操作: 如果一个操作不是原子性的,即不能保证在执行期间不被中断,并且可能在其执行过程中被其他线程干扰,那么在多线程环境下,可能会导致部分执行的结果对其他线程是可见的,从而破坏了程序的预期行为。

  4. 死锁(Deadlock): 死锁发生在多个线程互相等待对方持有的资源而无法继续执行的情况。如果线程在等待共享资源时不释放已占有的资源,可能会导致整个系统的停顿。

  5. 资源耗尽: 如果线程在使用共享资源时没有适当的释放或管理,可能会导致资源的过度消耗,最终导致系统的崩溃或性能下降。

  6. 信号量错误: 当多个线程同时操作信号量或其他同步原语时,如果没有正确地进行加锁和解锁操作,可能会导致信号量的计数出错或者条件变量的错误使用,从而引发程序异常。

在Java当中,保证线程安全的情况有:

  1. 使用锁机制:锁机制是一种用于控制多个线程对共享资源进行访问的机制。在 Java 中,锁机制主要有两种:synchronized 关键字和 Lock 接口。synchronized 关键字是 Java 中最基本的锁机制,它可以用来修饰方法或代码块,以实现对共享资源的互斥访问。而 Lock 接口是 Java5 中新增的一种锁机制,它提供了比 synchronized 更强大、更灵活的锁定机制,例如可重入锁、读写锁等;
  2. 使用线程安全的容器:如 ConcurrentHashMap、Hashtable、Vector。需要注意的是,线程安全的容器底层通常也是使用锁机制实现的;
  3. 使用本地变量:线程本地变量是一种特殊的变量,它只能被同一个线程访问。在 Java 中,线程本地变量可以通过 ThreadLocal 类来实现。每个 ThreadLocal 对象都可以存储一个线程本地变量,而且每个线程都有自己的一份线程本地变量副本,因此不同的线程之间互不干扰。

三.创建多线程的方式

在Java当中,创建线程有五种方式:

  1. 继承Thread类
    1. Java 中所有的线程都是通过继承 Thread 类来实现的。要创建一个线程,你可以创建一个新的类,继承自 Thread,并重写 run() 方法来定义线程的主体逻辑。
    2. public class MyThread extends Thread {public void run() {// 线程的主体逻辑System.out.println("Thread running");}public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 启动线程}
      }
      

  2. 实现Runnalbe接口
    1. 另一种创建线程的方式是实现 Runnable 接口,并将其作为参数传递给 Thread 类的构造方法。
    2. public class MyRunnable implements Runnable {public void run() {// 线程的主体逻辑System.out.println("Runnable running");}public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start(); // 启动线程}
      }
      

  3. 使用匿名内部类
    1. 如果线程的逻辑比较简单,可以使用匿名内部类来创建和启动线程。
    2. public class ThreadExample {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {public void run() {// 线程的主体逻辑System.out.println("Anonymous thread running");}});thread.start(); // 启动线程}
      }
      

  4. 使用Callable 和 Future
    1. Callable 接口允许线程返回结果,并且可以通过 Future 来获取结果。
    2. import java.util.concurrent.Callable;
      import java.util.concurrent.FutureTask;public class CallableExample {public static void main(String[] args) throws Exception {Callable<Integer> callable = new Callable<Integer>() {public Integer call() {// 线程的主体逻辑return 123;}};FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread thread = new Thread(futureTask);thread.start();int result = futureTask.get(); // 获取线程返回的结果System.out.println("Result: " + result);}
      }
      
  5. 使用线程池
    1. 在实际开发中,通常推荐使用线程池来管理和复用线程,而不是直接创建和启动线程。Java 提供了 ExecutorServiceThreadPoolExecutor 等实现线程池的类。
    2. import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;public class ThreadPoolExample {public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executor.submit(() -> {// 线程的主体逻辑System.out.println("Thread running in pool");});}executor.shutdown(); // 关闭线程池}
      }
      

以上是几种常见的创建线程的方式。在选择使用哪种方式时,需要根据具体的需求和场景来决定,例如是否需要线程返回结果、是否需要管理大量线程等。

   

四.线程池的讲解            

        线程池是在并发编程中非常重要的概念,它能有效地管理和复用线程,提高程序的性能和资源利用率。在 Java 中,线程池由 ExecutorService 接口和其实现类 ThreadPoolExecutor 来实现。Spring 项目中,会使用代码可读性更高的 ThreadPoolTaskExecutor 来创建线程池

为什么使用线程池呢?

  1. 降低资源消耗:通过复用线程,避免线程创建和销毁的开销,减少了系统的资源消耗。

  2. 提高响应速度:当有任务到达时,可以立即执行,而不必等待新线程创建。

  3. 提高线程的可管理性:可以限制线程数量,防止因为过多线程导致系统资源耗尽或性能下降。

public static void main(String[] args) {// 任务的具体方法Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("当前任务被执行,执行时间:" + new Date() +" 执行线程:" + Thread.currentThread().getName());try {// 等待 1sTimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}};// 创建线程,线程的任务队列的长度为 1ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),new ThreadPoolExecutor.DiscardPolicy());// 添加并执行 4 个任务threadPool.execute(runnable);threadPool.execute(runnable);threadPool.execute(runnable);threadPool.execute(runnable);// 线程池执行完任务,关闭线程池threadPool.shutdown();
}
import java.util.concurrent.*;public class ThreadPoolExample {public static void main(String[] args) {// 核心线程数为 3,最大线程数为 5,空闲线程存活时间为 10 秒// 使用 ArrayBlockingQueue 作为工作队列,容量为 10ThreadPoolExecutor executor = new ThreadPoolExecutor(3,  // corePoolSize5,  // maximumPoolSize10, // keepAliveTimeTimeUnit.SECONDS, // 时间单位new ArrayBlockingQueue<>(10) // 工作队列);// 设置线程工厂,用于创建线程时自定义线程属性executor.setThreadFactory(r -> {Thread thread = new Thread(r);thread.setName("CustomThread-" + thread.getId());return thread;});// 设置拒绝策略为默认的 AbortPolicyexecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());// 提交任务给线程池执行for (int i = 0; i < 10; i++) {final int taskNumber = i;executor.execute(() -> {System.out.println("Task " + taskNumber + " executed by " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟任务执行} catch (InterruptedException e) {e.printStackTrace();}});}// 关闭线程池executor.shutdown();}
}

内部的参数:

  1. 核心线程数(corePoolSize)

    • 定义了线程池中保持的最小线程数,即使它们是空闲的。如果设置了核心线程数,即使线程池中没有任务要执行,也会保持这些核心线程的数量。
  2. 最大线程数(maximumPoolSize)

    • 定义了线程池中允许的最大线程数。当工作队列满了,并且有新的任务提交到线程池时,线程池可以创建更多的线程,直到达到最大线程数。超过最大线程数的任务会被拒绝执行(根据线程池的拒绝策略)。
  3. 空闲线程存活时间(keepAliveTime)

    • 当线程池中的线程数量超过核心线程数时,这些多余的空闲线程在被终止之前等待新任务的最长时间。超过这个时间后,多余的空闲线程会被销毁,直到线程池的大小缩减到核心线程数为止。
  4. 工作队列(workQueue)

    • 用于保存等待执行的任务的队列。不同的线程池实现可以使用不同类型的队列,如 LinkedBlockingQueueArrayBlockingQueueSynchronousQueue 等。这些队列控制着待执行任务的排队机制。
  5. 线程工厂(threadFactory)

    • 用于创建新线程的工厂。可以自定义线程的名称、优先级等属性。
  6. 拒绝策略(RejectedExecutionHandler)

    • 定义了当任务无法被接受执行时的策略。例如,可以选择直接抛出异常、丢弃任务、丢弃队列头部的任务或者由调用线程执行该任务等。

扩展资料:拒绝策略:

  • AbortPolicy:中止策略,线程池会抛出异常并中止执行此任务;
  • CallerRunsPolicy:把任务交给添加此任务的(main)线程来执行;
  • DiscardPolicy:忽略此任务,忽略最新的一个任务;
  • DiscardOldestPolicy:忽略最早的任务,最先加入队列的任务。

线程池的关闭:

三步:先调用shutdown()停止线程池进行接收新任务,使用awaitTermination() 方法来等待所有任务完成执行,使用shutdownNow()关闭运行的线程。

ExecutorService executor = Executors.newFixedThreadPool(10);
// 提交任务到线程池
for (int i = 0; i < 100; i++) {executor.submit(new MyTask());
}
// 关闭线程池
executor.shutdown();
try {// 等待所有任务完成执行if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {// 如果等待超时,强制关闭线程池executor.shutdownNow();}
} catch (InterruptedException e) {// 处理异常
}

五.volatile和synchronzied的区别

        volatilesynchronized 是 Java 中用于处理多线程并发访问的关键字。

volatile:一种轻量级的同步机制,可保证变量的可见性,一个线程修改之后,其他线程立即看见修改的值。

synchronized:重量级同步机制,提供了原子性和互斥性,同一时刻只有一个线程可以访问被synchronzied修饰的方法或者代码块。

volatile

  • 适用于单个变量的读写操作,例如标志位的更新,状态的判断等。
  • 不适合复合操作的原子性保证,如递增操作(i++),因为 volatile 不能保证原子性。

正常情况下,如果没有修饰常量,那么一个线程修改之后,另一个线程依然还是用最初始的值。 

public class VolatileExample {private volatile boolean flag = false;public void toggleFlag() {flag = !flag; // 状态切换操作}public boolean isFlagSet() {return flag; // 状态读取操作}public static void main(String[] args) {VolatileExample example = new VolatileExample();// 线程1:修改flagnew Thread(() -> {example.toggleFlag();System.out.println("Thread 1 toggled flag");}).start();// 线程2:读取flagnew Thread(() -> {while (!example.isFlagSet()) {// 等待flag变为true}System.out.println("Thread 2 detected flag is true");}).start();}
}

synchronzied

  • 适用于复合操作的原子性保证,比如一个方法内涉及多个字段的读写,或者需要保证读写操作的一致性。
  • 可以用于代码块或方法级别的同步,能够确保线程安全,但过多的使用可能导致性能问题。
public class SynchronizedExample {private Object lock = new Object();private int count = 0;public synchronized void increment() {count++;}public int getCount() {synchronized (lock) {return count;}}
}

二者区别:

  • 粒度

    • volatile 是用来修饰单个变量,保证变量的可见性。
    • synchronized 可以修饰代码块或方法,提供原子性和互斥性的操作。
  • 性能

    • volatile 比 synchronized 更轻量级,因为它不涉及线程的阻塞和唤醒。
    • synchronized 在涉及竞争时会引入线程的阻塞和唤醒,可能会影响性能。
  • 适用场景

    • 如果只需要保证变量的可见性,使用 volatile 是比较合适的选择。
    • 如果需要保证复合操作的原子性和线程安全,使用 synchronized 更为适当。

 六.synchronized 和 Lock的区别

        Java 中用于实现线程同步的机制,它们都可以保证线程安全。

synchronized

        synchronized 可用来修饰普通方法、静态方法和代码块,当一个线程访问一个被 synchronized 修饰的方法或者代码块时,会自动获取该对象的锁,其他线程将会被阻塞,直到该线程执行完毕并释放锁。

Lock

        Lock 是一种线程同步的机制,它与 synchronized 相似,可以用于控制对共享资源的访问。相比于 synchronized,Lock 的特点在于更加灵活,支持更多的操作。 定义了更多的方法:

  • lock():获取锁,如果锁已被其他线程占用,则阻塞当前线程。
  • tryLock():尝试获取锁,如果锁已被其他线程占用,则返回 false,否则返回 true。
  • tryLock(long timeout, TimeUnit unit):尝试获取锁,在指定的时间范围内获取到锁则返回 true,否则返回 false。
  • unlock():释放锁。

synchronized 和 Lock 主要的区别有以下几个方面:

  1. 锁的获取方式:synchronized 是隐式获取锁的,即在进入 synchronized 代码块或方法时自动获取锁,退出时自动释放锁;而 Lock 需要程序显式地获取锁和释放锁,即需要调用 lock() 方法获取锁,调用 unlock() 方法释放锁。
  2. 锁的性质:synchronized 是可重入的互斥锁,即同一个线程可以多次获得同一把锁,而且锁的释放也只能由获得锁的线程来释放;Lock 可以是可重入的互斥锁,也可以是非可重入的互斥锁,还可以是读写锁。
  3. 锁的粒度:synchronized 是以代码块和方法为单位进行加锁和解锁,而 Lock 可以精确地控制锁的范围,可以支持多个条件变量。
  4. 性能:在低并发的情况下,synchronized 的性能优于 Lock,因为 Lock 需要显式地获取和释放锁,而 synchronized 是在 JVM 层面实现的;在高并发的情况下,Lock 的性能可能优于 synchronized,因为 Lock 可以更好地支持高并发和读写分离的场景。

总的来说,synchronized 的使用更加简单,但是在某些场景下会受到性能的限制;而 Lock 则更加灵活,可以更精确地控制锁的范围和条件变量,但是使用起来比较繁琐。需要根据具体的业务场景和性能需求来选择使用哪种锁机制。

七.产生死锁的条件

        死锁(Dead Lock)指的是两个或两个以上的运算单元(进程、线程或协程),互相持有对方所需的资源,导致它们都无法向前推进,从而导致永久阻塞的问题就是死锁。

四大条件:

  • 互斥条件:指运算单元(进程、线程或协程)对所分配到的资源具有排它性,也就是说在一段时间内某个锁资源只能被一个运算单元所占用。
  • 请求和保持条件:指运算单元已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它运算单元占有,此时请求运算单元阻塞,但又对自己已获得的其它资源保持不放。
  • 不可剥夺条件:指运算单元已获得的资源,在未使用完之前,不能被剥夺。
  • 环路等待条件:指在发生死锁时,必然存在运算单元和资源的环形链,即运算单元正在等待另一个运算单元占用的资源,而对方又在等待自己占用的资源,从而造成环路等待的情况。

解决方案:破坏其中一个条件即可。

  1. 按照顺序加锁:尝试让所有线程按照同一顺序获取锁,从而避免死锁。
  2. 设置获取锁的超时时间:尝试获取锁的线程在规定时间内没有获取到锁,就放弃获取锁,避免因为长时间等待锁而引起的死锁。

八.Java当中常见的锁策略

        锁策略,用于对锁进行分类和指导锁的(具体)实现

1.乐观锁与悲观锁

        乐观锁:假设并发冲突的概率很低,因此不对共享资源加任何锁,而是直接进行操作。在更新数据时,会先比较数据版本(或者其他标识),如果版本匹配,则执行更新操作;如果版本不匹配,则认为数据已被其他线程修改,需要进行冲突处理(例如重试或者放弃操作)。实现方式:CAS

        悲观锁:核心思想是假设最坏的情况,即并发情况下一定会发生冲突,因此在访问数据前先获取锁。这种锁会阻塞其他试图获取相同资源的线程,直到当前线程释放锁。

2.读写锁

        允许多个线程同时读取共享资源,但是在写入时需要独占资源。这种机制在读操作远远多于写操作的场景中尤为有效,可以提高系统的并发性能。实现方式:

  1. 基于互斥量的实现

    • 使用两个互斥量(mutex),一个用于读操作的互斥量,一个用于写操作的互斥量。
    • 当有线程要进行写操作时,必须先获取写互斥量,同时阻止其他线程获取读互斥量;而读操作则只需要获取读互斥量,不阻塞其他读操作。
  2. 基于信号量的实现

    • 使用两个信号量(semaphore),一个用于控制读者数量,一个用于控制写者。
    • 读者获取读信号量时,如果没有写者正在进行写操作,则允许进入;写者获取写信号量时,需要等待所有读者退出,确保独占写权限。
  3. 基于原子操作的实现

    • 在一些高级语言中(如Java),可以使用原子操作来实现读写锁。通过原子操作可以实现非阻塞的并发控制,效率较高。

3.互斥锁

        互斥锁是最基本的锁类型,用于确保在同一时刻只有一个线程可以访问共享资源。一旦一个线程获得了互斥锁,其他试图获取该锁的线程将被阻塞,直到持有锁的线程释放它。在 Java 中,可以使用 ReentrantLock 类实现互斥锁,也可以使用 synchronized 关键字实现隐式的互斥锁。

4.自旋锁

        一种基于忙等待的锁,线程在获取锁时不会立即阻塞,而是循环检测锁的状态,直到获取到锁为止。适用于锁保护时间非常短暂的情况。可以使用 AtomicBoolean 或者 AtomicInteger 结合 compareAndSet 方法来实现简单的自旋锁。

5.重入锁

        可以被同一个线程多次获取的锁,线程每次获取锁时计数器加一,每次释放锁时计数器减一,直到计数器为零才完全释放锁。相比于 synchronized 关键字,重入锁提供了更灵活的锁获取与释放方式,例如可以设置超时时间、中断响应等。

6.公平锁与非公平锁

        线程按照请求锁的顺序依次获取锁,而非公平锁则允许线程插队获取锁,可能会导致某些线程长时间等待。ReentrantLock 可以通过构造函数指定是否使用公平策略。

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

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

相关文章

C基础day8

一、思维导图 二、课后习题 #include<myhead.h> #define Max_Stu 100 //函数声明 //学生信息录入函数 void Enter_stu(int *Num_Stu,char Stu_name[][50],int Stu_score[]); //查看学生信息 void Print_stu(int Num_Stu,char Stu_name[][50],int Stu_score[]); //求出成绩…

运维锅总详解进程、内核线程、用户态线程和协程

I/O 密集型应用、计算密集型应用应该用什么实现&#xff1f;进程、内核线程、用户态线程、协程它们的原理和应用场景又是什么&#xff1f;如何组合它们才能让机器性能达到最优&#xff1f;它们的死锁和竞态又是什么&#xff1f;如何清晰地表示它们之间的关系&#xff1f;希望读…

红日靶场----(三)2.漏洞利用

上期的通过一句话木马实现对目标主机的持久后门 我使用的是蚁剑&#xff0c;蚁剑安装及使用参考&#xff1a; 下载地址&#xff1a; GitHub - AntSwordProject/AntSword-Loader: AntSword 加载器 安装即使用&#xff1a; 1. 快速入门 语雀 通过YXCMS的后台GETSHELL 利用…

Dify工作流中的变量聚合节点

一.定义 变量聚合节点&#xff08;原变量赋值节点&#xff09;负责整合不同分支的输出结果&#xff0c;确保无论哪个分支被执行&#xff0c;其结果都能通过一个统一的变量来引用和访问。这在多分支的情况下非常有用&#xff0c;可将不同分支下相同作用的变量映射为一个输出变量…

剖析自闭症孩子玩手的独特之处

自闭症孩子玩手的行为常常具有一些较为独特的特点。 重复性是一个显著的特征。他们可能会以一种几乎相同的方式、节奏和频率反复地摆弄自己的手&#xff0c;例如不停地握拳、张开&#xff0c;或者持续地旋转手腕。 动作的单调性也是常见的。玩手的方式可能较为单一&#xff0c;…

力扣 24两两交换链表中节点

画图 注意有头结点 注意判断时先判断cur->next ! nullptr,再判断cur->next->next ! nullptr 注意末尾返回dumyhead->next&#xff0c;用新建result指针来接并返回 class Solution { public:ListNode* swapPairs(ListNode* head) {ListNode *dummyhead new List…

小技巧(更新中)

1.Pycharm使用小技巧pycharm的使用小技巧1---快速找到模块内的函数和类&#xff0c;快速定位查看的模块所在位置_pycharm怎么查找某个函数-CSDN博客 2. Python库之requirments Python库安装之requirements.txt, environment.yml_python requirements-CSDN博客 3.执行.sh脚本的…

vue中v-if与v-show的区别

在 Vue.js 中&#xff0c;v-if 和 v-show 都是用来控制元素显示与隐藏的指令&#xff0c;但它们之间有几个关键的区别&#xff1a; 直接上图 一. 条件渲染方式不同 v-if&#xff1a; 真正的条件渲染&#xff1a;v-if 指令会根据表达式的真假来销毁或重新创建 DOM 元素及其…

LeetCode 88.合并两个有序数组 C写法

LeetCode 88.合并两个有序数组 C写法 思路&#xff1a; ​ 由题nums1的长度为mn&#xff0c;则我们不需要开辟新的数组去存储元素。题目要求要有序合并&#xff0c;于是可以判断哪边数更大&#xff0c;将更大的数尾插在nums1中。 ​ 定义三个变量来控制下标&#xff0c;end1控…

AI绘画工具Stable Diffusion神级插件InstantID,AI换脸完美版!

随着AI绘画技术的不断迭代&#xff0c;AI换脸也日臻完美。 从路线上看&#xff0c;主要有两条路线&#xff0c;一是一张图换脸&#xff0c;优点是操作简便&#xff0c;缺点是换个姿势的时候&#xff0c;往往不太像&#xff0c;roop等插件是基于这个思路&#xff1b;二是炼制专…

UWB:FiRa Consortium UCI Generic Technical Specification v1.1.0(1)- UCI架构和通用数据包头

FiRa fine ranging 精确测距 为了UWB产业能够蓬勃发展&#xff0c;各个公司的产品必须互联互通&#xff0c;不然就是一盘散沙&#xff0c;成不了气候。于是成立了FiRa UWB联盟&#xff0c;相当于WiFi里面的WiFi alliance&#xff08;WiFi联盟&#xff09;&#xff0c;蓝牙里面…

uniapp x — 跨平台应用开发的强大助力

摘要&#xff1a; 随着前端技术的不断演进&#xff0c;跨平台应用开发框架成为了提升开发效率、降低开发成本的重要工具。uni-app以其跨平台兼容性和丰富的功能受到了开发者的广泛青睐。然而&#xff0c;随着应用需求的日益增长&#xff0c;对框架的功能和性能要求也在不断提高…

洛谷P1498 南蛮图腾[递归好题]

南蛮图腾 题目背景 自从到了南蛮之地&#xff0c;孔明不仅把孟获收拾的服服帖帖&#xff0c;而且还发现了不少少数民族的智慧&#xff0c;他发现少数民族的图腾往往有着一种分形的效果&#xff0c;在得到了酋长的传授后&#xff0c;孔明掌握了不少绘图技术&#xff0c;但唯独…

【Android】kotlin jdk版本冲突与Kotlin依赖管理插件

1、androidx.activity&#xff1a;activity&#xff1a;1.8.0 依赖版本错误问题 *依赖项“androidx.activity&#xff1a;activity&#xff1a;1.8.0”要求依赖它的库和应用针对版本 34 或更高版本 Android API 进行编译。&#xff1a;app 目前是针对 android-33 编译的。此外…

10个JavaScript One-Liners让初学者看起来很专业

原文链接&#xff1a;https://pinjarirehan.medium.com/10-javascript-one-liners-for-beginner-developers-to-look-pro-b9548353330a 原文作者&#xff1a;Rehan Pinjari 翻译&#xff1a;小圆 你是不是在辛苦码字时&#xff0c;看到别人轻松甩出一行 JavaScript 就搞定难题…

苹果笔记本电脑能玩哪些游戏 苹果电脑可以玩的单机游戏推荐

苹果笔记本有着优美的外观和强大的性能。用户不仅可以使用苹果笔记本办公、剪辑&#xff0c;越来越多的用户开始关注苹果笔记本在游戏领域的表现&#xff0c;尤其是在大型游戏方面。本文将为你详细介绍苹果笔记本都能玩什么游戏&#xff0c;以及为你推荐苹果电脑可以玩的单机游…

快到不可思议!Internet Download Manager下载器,让你的网速飞起来!

&#x1f31f; 快到不可思议&#xff01;Internet Download Manager下载器&#xff0c;让你的网速飞起来&#xff01;&#x1f680; 嗨喽&#xff0c;各位csdn的朋友们&#xff01;&#x1f44b;今天我要跟大家分享一个我超爱的下载神器——Internet Download Manager&#xff…

基于uni-app与图鸟UI的知识付费小程序模板

一、项目概述 在知识经济蓬勃发展的背景下&#xff0c;移动互联网成为知识传播与消费的重要渠道。本项目旨在利用前沿的前端技术栈——uni-app及高效UI框架图鸟UI&#xff0c;打造一款集多功能于一体的、面向广大求知者的知识付费平台移动端模板。该模板旨在简化开发流程&…

Java:分批查询

前言 最近遇到一个场景问题&#xff0c;就是基于SQL server数据库的规范&#xff0c;查询条件in如果个数超过2100个就会报错。由于是ORM映射框架采用的MybatisPlus 起初我想到的是基于 MybatisPlus 的 参数分割&#xff0c;测试还是不行&#xff0c;于是就直接基于 mybatis xm…

线性回归笔记

https://blog.51cto.com/u_16213589/7682076 残差图 多元回归-最小二乘法-残差分析笔记 一.多元线性回归模型的假设 我们需要进行以下六个假设&#xff0c;这些假设是经典的多元线性回归模型有效的前提&#xff1a; 1、因变量Y和自变量X1&#xff0c;X2&#xff0c;…&#…