【JavaEE】锁策略和CAS

news2024/11/24 6:00:59

    🔥个人主页: 中草药

🔥专栏:【Java】登神长阶 史诗般的Java成神之路


💰一.常见的的锁策略

        锁策略(Locking Strategy)是指在多线程环境中,为了控制对共享资源的访问,确保数据一致性和线程安全,而采用的一系列机制和规则。在并发编程中,锁是管理共享资源访问的核心工具,它防止了多个线程同时修改同一份数据,从而避免了数据竞争和不一致性的问题。不同的锁策略有着不同的特性和适用场景,它们在并发控制、性能、复杂性和可扩展性方面存在差异。

1.乐观锁vs悲观锁

乐观锁

        乐观锁基于“乐观主义”假设,认为数据不太可能被并发修改,因此在读取数据时不会锁定数据,只有在更新数据时才检查数据是否被其他事务修改过。乐观锁通常使用版本号或时间戳来实现,每次更新数据时都会检查版本号是否与读取时相同,如果不同,则表明数据已经被其他事务修改,本次更新将被拒绝。

        乐观锁的优点在于它减少了锁的使用,提高了系统的并发性能,尤其适合读多写少的场景。缺点是如果多个事务同时尝试更新同一份数据,可能会导致更新失败,需要重新读取数据并再次尝试更新,这被称为“重试”。

悲观锁

        悲观锁基于“悲观主义”假设,认为数据很可能被并发修改,因此在读取或写入数据之前,会先锁定数据,阻止其他线程或进程的并发访问。悲观锁通过在事务开始时获取锁并在事务结束时释放锁来实现。常见的悲观锁机制包括:

  • 排他锁(Exclusive Locks):写操作通常需要排他锁,不允许任何其他读写操作同时进行。
  • 共享锁(Shared Locks):读操作可以获取共享锁,允许多个读操作同时进行,但不允许写操作。

        悲观锁的优点在于它能保证数据的一致性,避免了脏读和不可重复读等问题。然而,它的缺点也很明显,主要是锁的等待时间可能较长,容易造成死锁,且降低了系统的并发度。

乐观锁vs悲观锁

特征乐观锁(Optimistic Locking)悲观锁(Pessimistic Locking)
基本假设假设数据不太可能被并发修改假设数据很可能被并发修改
实现机制使用版本号或时间戳进行并发控制通过锁定数据防止并发修改
锁的使用不在读取数据时使用锁在读取或写入数据时使用锁
更新策略更新时检查版本号,如果冲突则重试更新前锁定,更新后释放锁
并发性能高,因为它减少了锁的使用低,因为锁的等待时间可能较长
数据一致性较低,可能发生重试高,锁定期间确保数据一致性
死锁风险无,因为没有锁的等待高,尤其是在复杂的事务中
资源消耗低,较少的系统调用和上下文切换高,较多的系统调用和上下文切换
适用场景读多写少,对实时性要求较高的系统写操作频繁,对数据一致性要求极高的系统
复杂性实现上相对复杂,需要处理版本控制和冲突实现上相对简单,依赖于锁机制
Synchronized 初始使用乐观锁策略,当发现锁竞争比较频繁的时候,就会自动切换为悲观锁策略

2.轻量级锁vs重量级锁

轻量级锁

  • 定义:轻量级锁是JVM为了提高锁的性能而引入的一种机制,它试图在没有线程竞争的情况下避免使用重量级锁的开销。轻量级锁在Java 6之后的版本中默认启用。

  • 实现:轻量级锁使用了基于CAS(Compare and Swap)的原子操作。当一个线程尝试获取锁时,它会通过CAS操作将当前线程ID写入锁对象的Mark Word中。如果CAS操作成功,那么线程获得了锁;如果失败,则进入下一步骤。

  • 特点

    • 性能较高,因为它避免了操作系统层面的线程挂起和唤醒,减少了上下文切换的开销。
    • 当锁竞争较少时,轻量级锁的效果最佳。
    • 如果在一定次数的自旋后仍未能获取锁,轻量级锁会膨胀为重量级锁。
    • 少量内核态用户态的切换
    • 不太容易引发线程的调度

重量级锁

  • 定义:重量级锁是传统的锁实现,当一个线程获取重量级锁时,其他试图获取该锁的线程将被阻塞,直到锁被释放。

  • 实现:重量级锁的获取和释放涉及操作系统层面的线程挂起和唤醒,这通常需要从用户态切换到内核态,开销较大。

  • 特点

    • 性能较低,因为涉及到线程挂起和唤醒的开销。
    • 在锁竞争激烈时,重量级锁可以确保数据的一致性和线程安全。
    • 当线程竞争锁时,重量级锁可以更好地保证数据的完整性,但牺牲了性能。
    • 大量内核态用户态的切换
    • 很容易引发线程的调度

轻量级锁vs重量级锁

特征轻量级锁(Lightweight Lock)重量级锁(Heavyweight Lock)
实现机制基于CAS的原子操作操作系统层面的线程挂起和唤醒
性能在锁竞争较少时性能高锁竞争激烈时,性能低
上下文切换减少了上下文切换需要进行上下文切换
资源消耗相对较低相对较高
适用场景读多写少,锁竞争较小的场景写操作频繁,对数据一致性要求高的场景
锁升级当锁竞争加剧时,轻量级锁可能升级为重量级锁无升级过程

synchronized 开始是⼀个轻量级锁. 如果锁冲突⽐较严重, 就会变成重量级锁

3.自旋锁vs挂起等待锁

自旋锁(Spin Lock)

  • 定义:当一个线程试图获取一个已经被其他线程持有的锁时,自旋锁会让当前线程在一个循环中不断检查锁的状态,直到锁变为可用状态。

  • 特点

    • 避免了线程的挂起和唤醒,减少了线程上下文切换的开销。
    • 适用于锁持有时间非常短的场景,因为在这种情况下,自旋等待的CPU消耗可能比线程挂起和唤醒的开销要小。
    • 如果锁的持有时间较长,或者竞争锁的线程数量很多,自旋锁可能会导致大量的CPU空转,浪费计算资源。

挂起等待锁(Sleeping Lock)

  • 定义:当一个线程试图获取一个已经被其他线程持有的锁时,挂起等待锁会让当前线程进入等待状态,直到锁变为可用。这通常涉及到线程的挂起和唤醒。

  • 特点

    • 减少了CPU的空转,节省了计算资源。
    • 适用于锁持有时间较长,或者线程竞争较为激烈的场景。
    • 线程的挂起和唤醒涉及到操作系统层面的操作,会有一定的开销,包括上下文切换。

自旋锁vs挂起等待锁

特征自旋锁(Spin Lock)挂起等待锁(Sleeping Lock)
CPU使用可能导致CPU空转,消耗CPU资源节省CPU资源,避免空转
上下文切换减少了线程的上下文切换增加了线程的上下文切换
适用场景锁持有时间短,竞争不激烈锁持有时间长,或竞争激烈
开销锁竞争大时CPU开销大锁竞争大时上下文切换开销大

4.公平锁vs非公平锁

公平锁

  • 定义:公平锁遵循先进先出(FIFO)的原则,确保请求锁的线程按照它们请求锁的顺序来获取锁。这意味着如果一个线程在另一个线程之前请求了锁,那么它将在那个线程之前获得锁,除非那个线程释放了锁。

  • 特点

    • 提供了更高的公平性,避免了后请求锁的线程“插队”。
    • 由于必须检查等待队列中的所有线程,因此在锁的竞争中可能会有更高的性能开销。
    • 在线程交替请求锁的场景下,公平锁可以避免线程饥饿,即某个线程长期得不到锁的情况。

非公平锁

  • 定义:非公平锁不保证锁的获取顺序,它允许后请求锁的线程有可能比先请求锁的线程更快地获得锁。在默认情况下,ReentrantLock就是非公平锁。(后文详细介绍)

  • 特点

    • 性能通常优于公平锁,因为它在获取锁时不需要遍历等待队列,而是直接尝试获取。
    • 可能会出现线程饥饿现象,即某些线程长时间无法获取到锁。
    • 在锁的竞争较少的情况下,非公平锁的性能优势更加明显。

公平锁vs非公平锁

特征公平锁(Fair Lock)非公平锁(Unfair Lock)
获取顺序按照请求锁的顺序获取锁不保证获取顺序,可能存在“插队”现象
性能锁竞争激烈时性能可能较低锁竞争较少时性能较高
公平性高,避免线程饥饿低,可能存在线程饥饿
默认行为ReentrantLock不默认使用ReentrantLock的默认行为
应用场景锁竞争激烈,需避免线程饥饿锁竞争较少,追求高吞吐量

注意

  • 操作系统内部的线程调度就可以视为随机的,如果不做任何额外的限制,锁就是非公平锁,如果想要实现公平锁,就需要依赖额外的数据结构,来记录线程的先后顺序
  • 公平锁和非公平锁没有好坏之分,关键还看适用场景 

5.可重入锁vs不可重入锁

可重入锁(Reentrant Lock)

  • 定义:可重入锁允许一个线程多次获取同一把锁,而不会导致死锁。每当一个线程获取锁时,锁的计数器会递增,当该线程释放锁时,计数器递减,直到计数器归零,锁才真正被释放。

  • 特点

    • 支持递归锁定,即一个线程可以在已经获取锁的情况下再次获取锁。
    • 避免了因递归锁定而导致的死锁问题。
    • 在多线程环境中,特别当线程需要多次进入同一临界区时,可重入锁提供了灵活性和安全性。

不可重入锁(Non-reentrant Lock)

  • 定义:不可重入锁不允许一个线程多次获取同一把锁。如果一个线程已经获取了一把锁,再次尝试获取这把锁会导致阻塞,直到锁被另一个线程释放。

  • 特点

    • 简化了锁的管理,因为不需要跟踪锁的嵌套级别。
    • 如果不恰当地使用递归锁定,不可重入锁可以防止死锁,但这也限制了它的使用场景。
    • 在单线程多次访问同一临界区的场景下,不可重入锁可能不如可重入锁灵活。

可重入锁vs不可重入锁

特征可重入锁(Reentrant Lock)不可重入锁(Non-reentrant Lock)
递归锁定支持,允许多次获取同一把锁不支持,再次获取同一把锁会导致阻塞
死锁预防内部机制可以避免递归锁定导致的死锁简化了锁管理,但限制了灵活性
灵活性高,适合复杂多线程场景低,适合简单或不需要递归锁定的场景
安全性高,避免了死锁较高,但在某些场景下可能过于限制
synchronized 是可重⼊锁

6.读写锁

读写锁(Read-Write Lock)是一种特殊的锁机制,它允许多个读操作同时进行,但写操作是独占的。读写锁的设计目的是为了提高并发性能,尤其是在读操作远远多于写操作的场景下。下面详细解释读写锁的工作原理及其在Java中的实现:

工作原理

读写锁维护了两把锁:一把读锁和一把写锁。

  • 读锁:允许多个线程同时获取,只要没有线程持有写锁。这意味着多个线程可以同时读取共享资源,只要没有线程正在进行写操作。

  • 写锁:是独占的,意味着在任何时刻,只能有一个线程持有写锁。当一个线程持有写锁时,其他所有线程(无论是读还是写)都无法获取锁,直到写锁被释放。

特点

  • 高并发性:读写锁提高了读操作的并发度,因为在没有写操作时,多个读线程可以同时访问共享资源。

  • 写操作独占:写操作总是独占的,确保了数据在写入时的一致性,防止了数据竞争条件。

  • 公平性:读写锁可以有不同的公平性实现。有些实现保证了读写操作的公平性,即按照请求顺序获取锁;有些则优先考虑写操作,以避免写饿死。

Java中的实现

在Java中,读写锁通过java.util.concurrent.locks.ReadWriteLock接口来实现,最常见的实现是ReentrantReadWriteLockReadWriteLock接口定义了readLock()writeLock()两个方法,分别用于获取读锁和写锁。

ReentrantReadWriteLock.ReadLock 类 表示一个读锁,这个对象提供了lock/unlock方法进行加锁解锁.

ReentrantReadWriteLock.WriteLock 类 表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.

  • 读加锁和读加锁之间,不互斥
  • 写加锁和写加锁之间,互斥
  • 读加锁和写加锁之间,互斥
Synchronized 不是读写锁.

🪙二.CAS

        比较并交换(Compare-and-Swap,简称CAS)是一种无锁算法的基本构建块,广泛应用于并发编程中,用于实现原子操作。CAS操作在多处理器架构中特别有用,因为它能够确保即使在多个线程或处理器同时尝试修改同一内存位置时,操作也能正确、原子地执行。下面详细讲述CAS的概念、工作原理以及在Java中的实现。

1.基本概念

CAS是一种硬件级别的原子操作,通常由处理器直接支持。它涉及三个操作数:内存位置(V)、期望的旧值(A)和新值(B)。CAS操作会比较内存位置V的当前值与期望的旧值A是否相等,如果相等,则将V的值原子地更新为新值B;如果不相等,则操作失败,返回当前的V值。由于CAS操作是原子的,这意味着在CAS操作过程中,不会有其他线程或进程能够干扰这个操作。

2.工作原理

  1. 加载值:CAS操作开始时,线程会加载内存位置V的当前值。

  2. 比较值:将加载的值与期望的旧值A进行比较。如果两者相等,则进行下一步;如果不等,CAS操作失败。

  3. 交换值:如果比较成功,CAS操作会原子地将V的值更新为新值B。

  4. 返回结果:CAS操作返回一个结果,指示操作是否成功。如果成功,通常返回新值;如果失败,返回旧值。

工作原理伪代码

下⾯写的代码不是原⼦的, 真实的 CAS 是⼀个原⼦的硬件指令完成的. 这个伪代码只是辅助理解 CAS 的⼯作流程.

boolean CAS(address, expectValue, swapValue) {
    if (&address == expectedValue) {
    &address = swapValue;
    return true;
    }
    return false;
}

3.CAS在Java中的实现

在Java中,CAS操作主要通过Unsafe类的compareAndSwapIntcompareAndSwapLongcompareAndSwapObject方法实现,这些方法提供了对底层硬件CAS操作的访问。然而,直接使用Unsafe类通常被认为是不推荐的,因为它破坏了Java的封装性和安全性。

更安全且推荐的方式是使用Java并发库中的Atomic类,如AtomicIntegerAtomicLongAtomicReference。这些类内部使用了CAS操作,但对外提供了更高级、更安全的API。

举例 

import java.util.concurrent.atomic.AtomicInteger;

public class demo5 {
    private static AtomicInteger count=new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement(); //count++
//                count.incrementAndGet(); ++count
//                count.getAndDecrement(); count--
//                count.decrementAndGet(); --count
//                count.getAndAdd(10)      count+=10
            }
        });

        Thread t2=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

4.CAS的局限性

尽管CAS操作提供了原子性,但它也有其局限性:

  1. ABA问题:如果一个值被多次设置为相同的值A,CAS操作可能误以为值没有被改变。解决这个问题通常需要使用版本号或标记值。

  2. 循环时间:在多线程环境中,如果多个线程尝试同时更新同一变量,可能会导致CAS操作反复失败,从而导致线程在自旋中消耗大量CPU时间。

  3. 性能问题:在高并发场景下,CAS操作的性能可能会下降,因为失败的CAS操作需要重试,这可能导致CPU空转。

尽管如此,CAS仍然是实现无锁数据结构和算法的关键技术,能够显著提高并发程序的性能和可伸缩性。

💳三.相关面试题

不做具体详细的拓展回答,仅做一个简答

1.你是怎么理解乐观锁和悲观锁的

        悲观锁认为多个线程访问同⼀个共享变量冲突的概率较⼤, 会在每次访问共享变量之前都去真正加锁.

        乐观锁认为多个线程访问同⼀个共享变量冲突的概率不大,并不会真的加锁,而是直接尝试访问数据. 在访问的同时识别当前的数据是否出现访问冲突.

        悲观锁的实现就是先加锁(⽐如借助操作系统提供的 mutex), 获取到锁再操作数据. 获取不到锁就等待。
        乐观锁的实现可以引入⼀个版本号. 借助版本号识别出当前的数据访问是否冲突. 
2.介绍一下读写锁
读写锁就是把读操作和写操作分别进⾏加锁.
读锁和读锁之间不互斥.
写锁和写锁之间互斥.
写锁和读锁之间互斥.
读写锁最主要⽤在 "频繁读, 不频繁写" 的场景中.
3.什么是自旋锁,为什么要使用自旋锁策略,缺点是什么?
如果获取锁失败, ⽴即再尝试获取锁, ⽆限循环, 直到获取到锁为⽌. 第⼀次获取锁失败, 第⼆次的尝试 会在极短的时间内到来. ⼀旦锁被其他线程释放, 就能第⼀时间获取到锁.
相⽐于挂起等待锁,
优点: 没有放弃 CPU 资源, ⼀旦锁被释放就能第⼀时间获取到锁, 更⾼效. 在锁持有时间⽐较短的场景
下⾮常有⽤.
缺点: 如果锁的持有时间较⻓, 就会浪费 CPU 资源.
4.讲解一下你自己了解的CAS机制
全称 Compare and swap, 即 "⽐较并交换". 相当于通过⼀个原⼦的操作, 同时完成 "读取内存, ⽐较是否相等, 修改内存" 这三个步骤. 本质上需要 CPU 指令的⽀撑.
5.ABA问题怎么解决
给要修改的数据引⼊版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期. 如 果发现当前版本号和之前读到的版本号⼀致, 就真正执⾏修改操作, 并让版本号自增; 如果发现当前版 本号比之前读到的版本号大, 就认为操作失败

💸四.总结与反思

神龟虽寿,犹有竞时。——曹操

在深入学习基本锁策略和CAS(Compare-and-Swap)操作的过程中,我对并发编程有了更深刻的理解。这些知识点不仅增强了我的编程技能,还帮助我更好地应对多线程环境下的挑战。以下是我在学习过程中的总结与反思。

基本锁策略

基本锁策略包括悲观锁、乐观锁、自旋锁、挂起等待锁、可重入锁、不可重入锁、公平锁、非公平锁、轻量级锁和重量级锁等。每种锁策略都有其独特的应用场景和优缺点。

  1. 悲观锁与乐观锁

    • 悲观锁假设数据很可能被并发修改,因此在读取或写入数据之前会锁定数据,确保数据的一致性。
    • 乐观锁假设数据不太可能被并发修改,因此在读取数据时不锁定数据,只有在更新数据时才检查数据是否被其他事务修改过。
    • 选择哪种锁策略取决于具体的应用场景和需求,如数据访问模式、并发程度、对数据一致性的要求等。
  2. 自旋锁与挂起等待锁

    • 自旋锁在尝试获取锁失败时不会放弃CPU,而是持续循环尝试获取锁,适用于锁持有时间短的情况。
    • 挂起等待锁在尝试获取锁失败时会释放CPU,等待锁可用后再获取,适用于锁持有时间长的情况。
    • 选择自旋锁还是挂起等待锁需要考虑锁的持有时间和线程竞争的程度。
  3. 可重入锁与不可重入锁

    • 可重入锁允许一个线程多次获取同一把锁,而不会导致死锁。
    • 不可重入锁不允许一个线程多次获取同一把锁,适用于不需要递归锁定的简单场景。
    • 选择哪种锁取决于程序设计的需求,如是否存在线程需要多次进入同一临界区的情况。
  4. 公平锁与非公平锁

    • 公平锁按照线程请求锁的顺序来分配锁,保证了锁的公平性。
    • 非公平锁不保证获取锁的顺序,可能存在线程饥饿的情况。
    • 在锁竞争激烈的情况下,公平锁可以避免线程饥饿,但在锁竞争较少的情况下,非公平锁的性能更高。
  5. 轻量级锁与重量级锁

    • 轻量级锁使用基于CASS的原子操作,在没有线程竞争的情况下避免使用重量级锁的开销。
    • 重量级锁涉及操作系统层面的线程挂起和唤醒,适用于锁竞争激烈的情况。
    • 选择哪种锁需要考虑锁的持有时间、线程竞争程度等因素。

CAS操作

CAS(Compare-and-Swap)操作是一种无锁算法的基本构建块,广泛应用于并发编程中。CAS操作涉及三个操作数:内存位置(V)、期望的旧值(A)和新值(B)。CAS操作会比较内存位置V的当前值与期望的旧值A是否相等,如果相等,则将V的值原子地更新为新值B;如果不相等,则操作失败,返回当前的V值。

  1. CAS操作的特点

    • CAS操作是原子的,能够在多线程环境下确保数据的一致性。
    • CAS操作可以用来实现无锁数据结构,如无锁队列、无锁栈等。
    • CAS操作也可能遇到ABA问题,即一个值被多次设置为相同的值A,可以通过使用版本号或标记值来解决这个问题。
  2. CAS操作的局限性

    • 在高并发场景下,CAS操作的性能可能会下降,因为失败的CAS操作需要重试,可能导致CPU空转。
    • CAS操作在多处理器架构中特别有用,但在单处理器系统中可能不是最佳选择。

        通过对基本锁策略和CAS操作的学习,我深刻认识到在多线程环境下正确使用这些机制的重要性。每种锁策略都有其适用的场景,选择合适的锁策略对于实现高效、稳定的并发程序至关重要。此外,CAS操作作为一种无锁算法的基础,对于提高并发程序的性能和可伸缩性同样重要。

        在实际应用中,我们需要根据具体的应用需求和系统特性来选择最合适的锁策略和CAS操作。此外,还需要注意锁策略之间的权衡,如性能与公平性、简单性与灵活性之间的平衡。通过不断实践和探索,我们可以更好地理解和应用这些知识,以解决实际问题。

        总之,学习基本锁策略和CAS操作不仅提高了我的并发编程技能,还让我意识到了在设计并发程序时需要考虑的诸多因素。在未来的学习和工作中,我会继续深入研究这些知识点,并努力将它们应用到实践中去。

🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀

以上,就是本期的全部内容啦,若有错误疏忽希望各位大佬及时指出💐

  制作不易,希望能对各位提供微小的帮助,可否留下你免费的赞呢🌸 

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

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

相关文章

「OC」探索CALayer:基础知识与实用技巧简要介绍

「OC」探索CALayer&#xff1a;基础知识与实用技巧简要介绍 文章目录 「OC」探索CALayer&#xff1a;基础知识与实用技巧简要介绍前言认识CALayerCALayer的相关属性 UIView和CALayer区别联系创建UIView和CALayer的原因 开始创建CALayer视图层级CALayers 和 Sublayersposition与…

javaweb-flex布局

1. flex布局原理 flex是flexible Box的缩写&#xff0c;意味着"弹性布局"&#xff0c;用来为盒子模型提供最大的灵活性&#xff0c;任何一个容器都可以指定为flex布局。 当我们为父盒子设为flex布局以后&#xff0c;子元素的float、clear和vertical-align属性都将失…

【质因数分解】将正整数分解质因数

将一个正整数分解成质因数&#xff0c;例如&#xff1a;输入90&#xff0c;打印输出 902*3*3*5 使用C语言实现&#xff1a; #include<stdio.h>int main(){int n,i;printf("请输入需要分解的正整数&#xff1a; ");scanf("%d",&n);for(i2;i<…

六. 部署分类器-deploy-classification-basic

目录 前言0. 简述1. 案例运行2. 代码分析2.1 main.cpp2.2 model.cpp 3. 补充说明结语下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程我们来学习课程第六章—部署分类器&#xff…

Flutter-自适用高度PageView

需求 在 Flutter 中&#xff0c;PageView 是一个非常常用的组件&#xff0c;能够实现多个页面的滑动切换。然而&#xff0c;默认的 PageView 高度是固定的&#xff0c;这在展示不同高度的页面时&#xff0c;可能会导致不必要的空白或内容裁剪问题。为了使 PageView 能够根据每…

Educational Codeforces Round 169 (Rated for Div. 2)(ABCDE)

A. Closest Point 签到 #define _rep(i,a,b) for(int i(a);i<(b);i) int n,m; int q[N]; void solve() {cin>>n;_rep(i,1,n)cin>>q[i];if(n!2)cout<<"NO\n";else if(abs(q[1]-q[2])!1)cout<<"YES\n";else cout<<"…

堆排序-优先级队列

我们用堆排来实现优先级队列&#xff0c;那么优先级队列是什么&#xff0c;就是 我们给每一个任务都添加一个优先级&#xff0c;优先级越高执行的越早我们用&#xff0c;但是我们怎么能按照顺序优先拿到优先级高的任务呢&#xff0c;我们可以用排序 来进行&#xff0c;也可以用…

Mybatis-Plus分页插件注意事项

使用Mybatis-Plus的分页插件进行分页查询时&#xff0c;如果结果需要使用<collection>进行映射&#xff0c;只能使用嵌套查询&#xff0c;而不能使用嵌套结果映射 嵌套查询和嵌套结果映射是Collection映射的两种方式&#xff0c;下面通过一个案例进行介绍 例如有room_i…

MyBatis源码系列3(解析配置文件,创建SqlSessionFactory对象)

创建SqlSessionFactory&#xff1b; 首先读取配置文件&#xff0c;使用构造者模式创建SqlSessionFactory对象。 InputStream inputStream Resources.getResourceAsStream("mybatis-config.xml");SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder…

C++面试基础系列-struct

系列文章目录 文章目录 系列文章目录C面试基础系列-struct1.C中struct2.C中struct2.1.同名函数2.2.typedef定义结构体别名2.3.继承 3.总结3.1.C和C中的Struct区别 4.struct字节对齐5.struct与const 关于作者 C面试基础系列-struct 1.C中struct struct里面只能放数据类型&#…

算法力扣刷题记录 八十六【47.全排列 II】

前言 回溯章节第12篇。 记录 八十四【46.全排列】初步学习了集合中无重复元素的排列求解。 本文&#xff1a;记录 八十六【47.全排列 II】当集合中有重复元素时&#xff0c;求解排列&#xff1b; 一、题目阅读 给定一个可包含重复数字的序列 nums &#xff0c;按任意顺序 返回…

VirtualBox安装Oracle Linux 7.9全流程

1.准备工作 1.1 VirtualBox下载 下载地址1&#xff1a; Downloads – Oracle VM VirtualBoxhttps://www.virtualbox.org/wiki/Downloads 下载地址2&#xff1a; https://www.oracle.com/virtualization/virtualbox/ 选择以上的任意一个地址都可下载到。 1.2 Oracle Linux 操作…

购物车系统设计方案

背景 在电商领域&#xff0c;购物车&#xff08;Shopping Cart&#xff09;扮演着至关重要的角色&#xff0c;它是连接用户浏览商品与最终完成购买行为的桥梁。 从两个视角来阐述&#xff0c;作为ToC的购物车&#xff0c;存在的意义&#xff1a; 从用户角度&#xff1a; 收…

ssm大学生实习管理系统的设计与实现-计算机毕业设计源码45837

摘 要 在信息时代&#xff0c;随着网络的快速发展&#xff0c;各个行业都离不开信息的处理。在这样的背景下&#xff0c;高校需要以学生管理信息为导向&#xff0c;并与学生实习的持续创新相结合。因此&#xff0c;设计一个高校学生实习管理系统就显得非常必要。 该系统采用了B…

维基百科向量搜索;简单易用的GraphRAG实现;友好的人工智能助手;AI的音乐多模态

✨ 1: Semantic Search on Wikipedia 维基百科向量搜索 为了证明 Upstash Vector 的可扩展性&#xff0c;Upstash在一个数据库中以 11 种语言&#xff08;144m 向量&#xff09;索引了整个维基百科 ◆ 超过700GB的数据 ◆ 快速语义搜索 ◆ 与维基百科聊天 为您提供了一款可…

Unity--AssetBundle AB包管理器

1.简介 AB包&#xff08;AssetBundle&#xff09;是Unity中用于资源管理的一种机制&#xff0c;它允许开发者将多个文件&#xff08;如纹理、模型、音频等&#xff09;打包成一个单独的文件&#xff0c;以便于在游戏运行时动态加载和卸载。 但是现在出现了最新的Addressable来…

Python匿名函数之lambda表达式使用详解

概要 在Python编程中,函数是组织代码和实现逻辑的基础单元。除了使用def关键字定义命名函数外,Python还提供了创建匿名函数的方式,即lambda表达式。lambda表达式是一种简洁的函数定义方式,通常用于需要简短函数的场景。本文将详细介绍Python匿名函数的概念、使用场景及其高…

基于51单片机的双机通信控制系统proteus仿真

地址&#xff1a; https://pan.baidu.com/s/1Y4wOJKOYf2E4JeEktyKdTw 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectro…

微分方程(Blanchard Differential Equations 4th)中文版Section3.1

3.1 PROPERTIES OF LINEAR SYSTEMS AND THE LINEARITY PRINCIPLE(线性系统问题与线性算子原理) 在第2章中,我们专注于研究微分方程组的定性和数值方法。之所以这样做,是因为我们很少能找到具有两个或更多个因变量的系统的明确解公式。唯一的例外是线性系统。在本章中,我们…

Linux·权限与工具-yum与vim

1. Linux软件包管理器 yum 1.1 什么是软件包 在Linux下安装软件&#xff0c;一个通常的办法是下载到程序的源代码&#xff0c;并进行编译&#xff0c;得到可执行程序。但这样做太麻烦了&#xff0c;于是有些人把一些常用的软件提前编译好&#xff0c;做成软件包(可以理解成Win…