Java大师成长计划之第18天:Java Memory Model与Volatile关键字
📢 友情提示:
本文由银河易创AI(https://ai.eaigx.com)平台gpt-4o-mini模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。
在Java多线程编程中,线程安全是一个常见且重要的议题。而理解Java内存模型(Java Memory Model, JMM)和volatile
关键字的作用,是开发线程安全程序的关键。Java内存模型描述了多线程环境下共享变量的行为,以及线程之间如何交互和同步。volatile
关键字则是保证变量可见性的一种简单而有效的手段。
本篇博客将详细讲解Java内存模型的工作原理、内存模型与变量可见性之间的关系、volatile
关键字的使用以及它如何影响并发程序的行为。
一. 什么是Java内存模型(JMM)?
1.1 内存模型概述
Java内存模型(Java Memory Model,JMM)是Java虚拟机(JVM)规范的一部分,旨在提供一个统一的、多线程环境下的内存访问模型。它描述了如何在并发程序中使用共享变量,并定义了线程之间如何进行通信以及这些操作如何影响变量的可见性和顺序性。JMM的设计目标是实现以下几个方面:
- 保证程序的安全性:确保多线程环境中对共享变量的访问和修改是安全的,避免数据的不一致和错误状态。
- 提升性能:通过允许JVM和编译器进行优化,提升多线程程序的执行效率,同时又不影响程序的正确性。
- 提高可理解性:为开发者提供清晰的模型,使其能够更好地理解在并发编程中可能出现的各种行为和问题。
1.2 JMM的核心概念
Java内存模型的设计围绕着几个关键的概念展开,这些概念是理解JMM如何工作的基础。
1.2.1 主内存与工作内存
在Java中,内存的管理主要分为两个层面:
-
主内存(Main Memory):这是所有共享变量的存储区域。所有的实例变量和静态变量都存储在主内存中。当线程需要访问共享变量时,必须先从主内存中读取数据。
-
工作内存(Working Memory):每个线程都有自己独立的工作内存,用于存储该线程的共享变量的副本。线程对共享变量的所有操作都是在自己的工作内存中进行的,线程直接操作工作内存中的数据,而不是直接访问主内存中的共享变量。
1.2.2 变量的可见性
Java内存模型保证了线程之间的可见性。具体来说,当一个线程对共享变量进行修改时,其他线程必须能够及时看到这个修改。JMM通过定义一系列的规则,确保操作的可见性。为了实现可见性,JMM要求线程在访问共享变量时,必须将变量的最新值从主内存读取到自己的工作内存中,并在修改后将修改的值刷新回主内存。
1.2.3 原子性
原子性是指操作不可分割,多个线程对共享变量的操作要么完全成功,要么完全不执行。JMM保证了一些特定的操作(如读取和写入基本类型的变量)是原子的,但对于复合操作(例如自增操作)则不一定具有原子性。在多线程环境中,如果多个线程同时对同一共享变量进行操作,而没有适当的同步机制,就可能导致数据的不一致性。
1.2.4 有序性
有序性描述了程序中指令执行的顺序。JMM允许JVM和编译器对指令进行重排序,以改善性能,但重排序不能破坏程序的逻辑顺序。JMM提供了一些规则来保证在特定条件下,程序的执行顺序是可控的,避免因重排序导致的错误执行。
1.3 JMM的保证
Java内存模型通过一些基本的规则和约束,确保了在多线程环境中对共享变量的操作是安全的。这些保证主要包括:
-
可见性保证:对于共享变量的所有写操作,必须保证在之后的读操作中可见。这意味着,一个线程对共享变量的修改必须被及时刷新到主内存中,从而确保其他线程读取到最新的值。
-
原子性保证:对于某些基本类型的操作,JMM确保这些操作的原子性。然而,对于复杂的复合操作,开发者需要使用适当的同步机制(如
synchronized
、Lock
等),以确保操作的原子性。 -
有序性保证:JMM允许线程在执行时对指令进行重排序,以提升性能,但重排序不能改变程序的逻辑结果。为了保证可见性和有序性,开发者可以使用同步机制来控制线程的执行顺序。
1.4 小结
Java内存模型(JMM)为多线程编程提供了一个框架,使得开发者能够理解和控制线程之间的交互。通过主内存与工作内存的分离、可见性、原子性和有序性等核心概念,JMM确保了在复杂的并发环境中,程序的行为是可预期的。理解这些概念及其背后的原理,能够帮助开发者更好地设计和实现线程安全的程序,避免常见的并发问题,提升应用程序的性能和稳定性。
二. 变量的可见性与JMM
2.1 可见性问题的出现
在多线程编程中,可见性是一个重要的概念,它指的是一个线程对共享变量所做的修改,能否被其他线程及时看到。当多个线程同时访问共享变量时,如果没有适当的同步机制,可能会导致可见性问题。
2.1.1 共享变量的修改
在Java中,线程在执行时会将共享变量的值从主内存加载到自己的工作内存中。此时,线程对该变量的所有操作都是在工作内存中进行的。若线程对共享变量进行了修改,这个修改首先反映在工作内存中,而不是立即写入主内存。这就可能导致其他线程在访问这个共享变量时,读取到的是一个过时的值。例如,考虑以下简单的代码片段:
public class VisibilityExample {private static boolean flag = false;public static void main(String[] args) throws InterruptedException {Thread writerThread = new Thread(() -> {try {// 暂停一秒钟,让readerThread有机会开始运行Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}flag = true; // 修改共享变量System.out.println("Writer Thread: flag is updated to true");});Thread readerThread = new Thread(() -> {while (!flag) {// 线程持续检查flag的值}System.out.println("Reader Thread: flag is now true, exiting loop");});writerThread.start();readerThread.start();writerThread.join();readerThread.join();}
}
在这个示例中,writerThread
在经过一秒后将flag
的值设置为true
,而readerThread
则不断检查flag
的值。在没有适当的同步机制下,readerThread
可能会一直读取到flag
的旧值(即false
),导致程序进入无限循环。这是因为readerThread
读取的可能是工作内存中缓存的旧值,而不是主内存中的最新值。
2.2 JMM中的可见性保证
Java内存模型为确保多线程环境中的可见性提供了一些保证和机制。JMM定义了一系列规则,确保线程之间对共享变量的修改是可见的。
2.2.1 主内存同步
JMM规定了共享变量的写操作需要被同步刷新到主内存,而读取操作需要从主内存加载最新值。当一个线程对共享变量进行修改时,它必须将修改后的值刷新回主内存,其他线程随后读取共享变量时就能获取到最新的值。这种机制保证了多个线程之间的可见性。
2.2.2 使用synchronized
和volatile
为了确保可见性,Java提供了几种方法来控制对共享变量的访问:
-
使用
synchronized
关键字:当一个线程进入synchronized
修饰的方法或代码块时,它会先获取锁,其他线程在尝试进入该区域时会被阻塞。synchronized
不仅保证了互斥访问,还确保了共享变量的可见性。当线程释放锁时,它会将修改过的变量刷新到主内存中,其他线程在获取锁时可以看到最新的值。public synchronized void updateFlag() {flag = true; // 线程对flag的修改是可见的 }
-
使用
volatile
关键字:volatile
关键字是Java中另一种确保可见性的方式。当一个变量被声明为volatile
时,JMM保证所有线程在访问该变量时,都会从主内存中读取最新的值,而不是从工作内存中读取。这消除了共享变量的可见性问题。private volatile boolean flag = false; // 确保flag的可见性public void updateFlag() {flag = true; // 其他线程能够立即看到这个更新 }
2.3 可见性与性能的权衡
在多线程应用中,可见性和性能之间往往存在一种权衡。虽然使用synchronized
或volatile
可以保证变量的可见性,但这通常会引入一定的性能开销。尤其是在高并发环境下,频繁的锁竞争会导致上下文切换,降低程序的整体性能。
2.3.1 锁的争用
当使用synchronized
修饰块或方法时,线程在获取锁时会发生阻塞。每当一个线程请求锁时,如果锁已经被其他线程持有,该线程将被迫等待,直到锁被释放。对于高并发的场景,这种竞争可能会导致性能瓶颈。
2.3.2 读写锁的使用
为了解决可见性和性能之间的矛盾,可以使用ReadWriteLock
。ReadWriteLock
允许多个线程并发读取共享变量,但在进行写操作时会独占访问。这样,当读操作远多于写操作时,可以大幅度提高程序的并发性能。
ReadWriteLock rwLock = new ReentrantReadWriteLock();public void read() {rwLock.readLock().lock();try {// 读取操作} finally {rwLock.readLock().unlock();}
}public void write() {rwLock.writeLock().lock();try {// 写入操作} finally {rwLock.writeLock().unlock();}
}
2.4 小结
在多线程编程中,变量的可见性是确保数据一致性的关键因素。Java内存模型通过定义主内存与工作内存的交互机制,保证了线程对共享变量的修改能够被其他线程及时看到。通过使用synchronized
和volatile
关键字,开发者能够有效地解决并发环境中的可见性问题。
然而,开发者在选择同步机制时,需要权衡可见性与性能之间的关系。在高并发的应用场景中,合理使用锁机制与优化策略,将有助于提高系统的整体性能,确保程序的可靠性和高效性。理解可见性原理及其在JMM中的应用,是编写高效和安全的多线程程序的基础。
三. volatile
关键字
3.1 volatile
的基本概念
volatile
是Java中用于修饰变量的一个关键字,主要作用是确保变量在多个线程之间的可见性。通过声明一个变量为volatile
,Java内存模型(JMM)确保所有线程对该变量的修改将立刻反映到主内存中,避免每个线程在其本地工作内存中操作一个陈旧的副本。
3.1.1 可见性与原子性
volatile
关键字提供的是可见性保证,但它并不保证操作的原子性。原子性指的是某个操作要么完全成功,要么完全失败,不会被中断。在多线程并发访问时,如果多个线程同时修改一个volatile
变量,volatile
不能保证这些操作的原子性,因此需要通过其他手段(如Atomic
类或synchronized
)来确保原子性。
3.2 volatile
的工作原理
volatile
的作用是在多线程环境下,确保对变量的读写操作直接发生在主内存上,而非线程的工作内存中。具体来说:
- 主内存更新:当一个线程修改
volatile
变量时,它的更新会直接写入主内存,而不是仅仅保存在该线程的工作内存中。 - 工作内存同步:当其他线程访问这个
volatile
变量时,它们会直接从主内存中读取最新的值,而不是从线程的工作内存中读取缓存的旧值。
这种机制避免了多个线程之间的内存可见性问题。例如,一个线程修改volatile
变量后,其他线程能够立刻看到这个修改,而不必等待线程间的同步操作。
3.3 volatile
关键字的使用场景
volatile
常常被用于处理简单的共享状态标志、指示值或者标识符。这些场景中,变量的变化不涉及复杂的计算或依赖,且对可见性的要求非常高。具体使用场景包括:
3.3.1 状态标志
volatile
可以用来作为控制线程执行的标志位。例如,一个线程用来标记是否需要退出,其他线程会检查该标志位的值。如果标志位为true
,则表示线程应该退出。这种状态标志的修改通常会被多个线程读取和更新,因此必须使用volatile
来确保所有线程都能看到该标志位的最新值。
public class VolatileFlagExample {private volatile boolean flag = false;public void setFlagTrue() {flag = true; // 线程1修改flag的值}public void checkFlag() {while (!flag) {// 线程2持续检查flag,若flag为false则继续}System.out.println("Flag is true, thread exits");}
}
在这个例子中,flag
被声明为volatile
,因此checkFlag
线程会看到setFlagTrue
线程对flag
变量的修改,避免了线程间的可见性问题。
3.3.2 单例模式(Double-Checked Locking)
volatile
在双重检查锁定(Double-Checked Locking)模式中也非常常见。在单例模式中,volatile
可以确保对象在创建时被正确初始化,且确保其他线程能看到该对象的最新状态,避免指令重排序造成的问题。
public class Singleton {private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
在这个单例模式的实现中,instance
被声明为volatile
,确保instance
的初始化过程不会被指令重排序打乱。volatile
确保当一个线程初始化了instance
变量后,其他线程能够立即看到该实例。
3.4 volatile
的限制
虽然volatile
在确保可见性方面非常有用,但它也有一些明显的限制。开发者在使用volatile
时,必须明确这些限制,否则可能导致程序中的问题。
3.4.1 不保证原子性
volatile
只提供了对单一变量的可见性保证,但它并不保证复合操作的原子性。复合操作指的是多步操作,例如count++
、i = i + 1
等。在这些操作中,volatile
并没有保证线程间操作的原子性,这样多个线程可能会在执行类似i = i + 1
这样的操作时产生竞争条件,从而导致数据不一致。
public class VolatileAtomicityExample {private volatile int count = 0;public void increment() {count++; // 不是原子操作}
}
在这个例子中,count++
是一个非原子操作,它涉及到读取count
的值、增加1、再写回count
。多个线程并发调用increment()
时,可能导致count
的值不准确。因此,在这种情况下,volatile
并不能保证线程安全,开发者应该使用AtomicInteger
类或者其他同步机制来保证原子性。
3.4.2 仅限于单一变量
volatile
只能保证单个变量的可见性问题,对于多个变量的同步控制,它无法处理。对于涉及多个共享变量的操作,开发者仍然需要使用Lock
或synchronized
来保证原子性和一致性。例如,以下代码需要保证多个变量的原子性,但volatile
无法保证:
public class VolatileMultipleVariables {private volatile int count;private volatile int sum;public void increment() {count++;sum += count; // 两个变量操作,无法通过volatile保证同步}
}
在这种情况下,count++
和sum += count
必须一起执行,才能保证数据一致性。然而,volatile
无法解决两个变量之间的协调问题,因此,应该使用Atomic
类或synchronized
来进行线程同步。
3.5 volatile
与synchronized
的对比
volatile
和synchronized
都可以用于多线程编程,但它们解决的问题有所不同:
-
volatile
:主要用于保证变量的可见性,它不会保证操作的原子性。如果线程之间仅仅是对某个变量的读取和写入,且没有其他复杂操作,可以考虑使用volatile
来确保可见性。 -
synchronized
:用于确保对资源的互斥访问,它不仅保证了可见性,还能确保操作的原子性。当多个线程访问共享资源并修改其值时,synchronized
能够避免竞态条件,确保程序的线程安全。
3.6 volatile
与现代并发库
现代并发编程中的一些高级工具类,通常会在内部利用volatile
关键字来解决可见性问题。例如,Java中的Atomic
类(如AtomicInteger
、AtomicLong
等)在保证原子性时,内部实现通常会结合使用volatile
。这些类提供了更高效、线程安全的方式来处理共享变量的访问,同时避免了使用synchronized
所带来的性能开销。
AtomicInteger atomicCount = new AtomicInteger(0);// 线程安全的递增操作
atomicCount.incrementAndGet();
在这个例子中,AtomicInteger
使用了volatile
来保证atomicCount
的可见性,并通过CAS(比较并交换)来保证原子性。因此,在高并发情况下,AtomicInteger
等类提供了比synchronized
更加高效的解决方案。
3.7 总结
volatile
关键字是Java中非常重要的并发工具,它通过保证共享变量的可见性,避免了线程间的内存可见性问题。然而,volatile
仅仅适用于简单的变量操作,不能保证复杂操作的原子性。在涉及多个变量或复杂操作时,开发者应该使用Atomic
类或synchronized
来确保线程安全。
通过合理使用volatile
,可以在高效的并发环境中,避免因缓存问题造成的数据不一致,提高程序的性能和可靠性。但同时,理解volatile
的限制,并在适当的场景下选择合适的并发控制机制,是开发高效并发程序的关键。
四. volatile
与sychronized
的比较
volatile
通常用于处理状态标志位、简单的共享数据或者信号量等场景,在这些场景中,变量的修改不涉及复杂的操作或者依赖关系。
4.1.2 synchronized
的功能
synchronized
关键字用于保证原子性和互斥性。它不仅保证了变量的可见性,还能确保对共享资源的访问是互斥的。具体来说:
synchronized
适用于需要保证数据一致性、线程安全且操作复杂的场景,例如数据库操作、文件读写、集合类操作等。
4.2 适用场景
4.2.1 volatile
适用场景
volatile
适用于以下场景:
4.2.2 synchronized
适用场景
synchronized
适用于以下场景:
4.3 性能差异
4.3.1 volatile
的性能
volatile
相比synchronized
具有较低的性能开销。原因如下:
不过,synchronized
的性能问题可以通过一些优化措施来减少,例如使用ReentrantLock
等高级锁机制,或通过细粒度锁的设计降低锁竞争。
4.4 volatile
与synchronized
的对比总结
特性 | volatile | synchronized |
---|---|---|
功能 | 主要保证变量的可见性 | 保证原子性、互斥性和可见性 |
适用场景 | 简单的状态标志、共享变量 | 复杂操作的同步控制、资源访问控制 |
原子性 | 不保证复合操作的原子性 | 保证对共享变量的操作是原子性的 |
性能开销 | 性能较低,不涉及锁竞争 | 性能较高,涉及锁的获取和释放,会有较大开销 |
线程安全性 | 只能保证单个变量的可见性,不能保证线程安全 | 保证代码块或方法内的线程安全,防止竞态条件 |
锁的粒度 | 无锁机制 | 基于锁的机制,涉及线程间的锁竞争 |
适用范围 | 适用于简单的标志位、状态控制等场景 | 适用于需要保障原子性、互斥性的复杂共享资源操作 |
-
在Java的多线程编程中,
volatile
和synchronized
是两个常用的关键字,它们用于处理线程之间的同步和共享变量的访问。虽然它们都涉及到并发控制,但它们的使用场景、工作原理、优缺点和性能开销存在显著差异。理解volatile
和synchronized
之间的区别,可以帮助开发者在不同的并发编程场景中做出更加高效和合理的选择。4.1 功能差异
4.1.1
volatile
的功能volatile
主要解决的是可见性问题。当一个变量被声明为volatile
时,JVM保证它在多个线程之间的值是即时可见的。具体来说: - 可见性:当一个线程修改了
volatile
变量的值,其他线程立刻能够看到这个修改。这是因为volatile
变量不会被缓存到线程的工作内存中,而是直接从主内存读取。 - 禁止重排序:
volatile
变量的写操作和读操作不能被JVM或编译器重排序。它保证了读写操作的顺序,避免了指令重排序带来的潜在问题。 - 互斥性:当一个线程获取到
synchronized
锁时,其他线程必须等待该线程释放锁才能继续执行。这避免了多个线程同时访问同一资源的竞争问题。 - 原子性:
synchronized
确保对共享变量的操作是原子性的,即在一个线程操作某个共享资源时,其他线程不能同时修改该资源。这样,synchronized
能够有效避免竞态条件和数据不一致问题。 - 可见性:
synchronized
还保证了锁的释放和获取操作时,变量的修改会同步到主内存,从而确保线程间对共享变量的可见性。 -
标志位或状态控制:当一个线程用来控制另一个线程的执行状态时,
volatile
是一个合适的选择。例如,线程安全的停止标志位(stopFlag
)通常会使用volatile
,以确保线程可以及时看到标志位的变化。private volatile boolean stopFlag = false;public void stopThread() {stopFlag = true; // 确保其他线程看到最新的stopFlag }
-
简单的共享变量:当多个线程需要共享一个值,并且该值的操作是简单的(如状态变量的读取和修改),
volatile
能够确保变量的可见性。 -
单例模式:
volatile
可用于单例模式中的“双重检查锁定”(Double-Checked Locking)方式,确保在多线程环境中安全地创建实例。private static volatile Singleton instance;public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance; }
-
复杂操作的线程安全:对于多个线程需要同时访问并修改某些共享资源的情况,
synchronized
确保操作的原子性和互斥性,避免了并发操作导致的数据不一致问题。 -
资源访问控制:在高并发环境下,当多个线程需要对同一资源进行独占访问时(例如文件、数据库等),
synchronized
确保只有一个线程能够在同一时间内访问该资源。 -
多步骤操作:当操作涉及多个步骤,且这些步骤必须作为一个原子操作执行时(例如读、修改、写等操作),
synchronized
能够保证这些操作不会被其他线程中断,避免了竞态条件。public synchronized void increment() {counter++; // 保证对counter的操作是原子性的 }
- 无锁机制:
volatile
没有锁的机制,因此不需要进行上下文切换或竞争锁,避免了由于锁带来的性能损耗。 - 简单的内存屏障:
volatile
通过内存屏障(memory barrier)确保变量的可见性,开销相对较小。 - 锁的竞争:当多个线程竞争同一锁时,线程会进行上下文切换,导致性能下降。尤其是在高并发环境下,锁竞争会显著影响系统性能。
- 内存同步:
synchronized
不仅仅保证了原子性,还确保了线程间的可见性,每次进入同步代码块时,都会强制刷新共享变量到主内存,从而带来额外的性能开销。 - 锁的粒度:
synchronized
的性能还与锁的粒度有关。粗粒度锁(锁范围大)可能导致较大的性能损失,而细粒度锁(锁范围小)虽然能提高并发性,但也会增加锁的管理开销。
4.5 小结
volatile
和synchronized
都在Java多线程编程中扮演着重要角色,但它们的使用场景、功能和性能特性有显著差异。
volatile
适用于需要保证变量可见性且操作简单的场景,它能有效减少同步的性能开销,但不保证原子性,适用于标志位、单例模式等。 synchronized
适用于需要保证线程安全和原子性的复杂操作,如多个线程修改共享数据的情况,它通过锁机制提供互斥性和可见性,但性能开销较大。
开发者应根据具体的场景选择合适的并发控制方式,在保证线程安全的同时,尽量降低性能损耗。理解volatile
和synchronized
的不同特点,可以帮助开发者在多线程编程中作出更加高效的决策。
五. 总结
理解Java内存模型(JMM)和volatile
关键字的使用,对于编写线程安全且高效的并发程序至关重要。JMM提供了多线程环境下的内存可见性、原子性和有序性的基础规范,而volatile
关键字则是实现变量可见性的高效工具。虽然volatile
能保证可见性,但它不能保证原子性,因此需要与其他机制(如Atomic
类或synchronized
)结合使用。
通过正确使用JMM和volatile
,我们可以避免并发编程中常见的错误,提高程序的可靠性和性能。掌握这些概念,将使你在多线程编程中游刃有余。
相关文章:
Java大师成长计划之第18天:Java Memory Model与Volatile关键字
📢 友情提示: 本文由银河易创AI(https://ai.eaigx.com)平台gpt-4o-mini模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。 在Java多线程编程中,线程…...

Lua再学习
因为实习的项目用到了Lua,所以再来深入学习一下 函数 函数的的多返回值 Lua中的函数可以实现多返回值,实现方法是再return后列出要返回的值的列表,返回值也可以通过变量接收到,变量不够也不会影响接收对应位置的返回值 Lua中传…...

GitLab搭建与使用(SSH和Docker)两种方式
前言 目前公共的代码仓库有很多,比如:git、gitee等等仓库但是我们在公司中,还是要搭建属于本公司自己的一个代码仓库,原因有如下几点 代码私密性,我们公司开发的代码保密性肯定一级重要,那么我们放到公网上,…...

Linux数据库篇、第零章_MySQL30周年庆典活动
MySQL考试报名网站 Oracle Training and Certification | Oracle 中国 活动时间 2025年 MySQL的30周年庆典将于2025年举行。MySQL于1995年首次发布,因此其30周年纪念日是2025年。为了庆祝这一里程碑,MySQL将提供免费的课程和认证考试,活动…...

Windows ABBYY FineReader 16 Corporate 文档转换、PDF编辑和文档比较
作为一名合格的工人,日常工作肯定离不开PDF文件,所以今天给大家找来了一款全新的PDF处理工具,保证能给你带来不一样的体验。 软件介绍 这是一个全能型的PDF处理器,集优秀的文档转换、PDF编辑和文档比较等功能于一身,…...
设计模式简述(十九)桥梁模式
桥梁模式 描述基本组件使用 描述 桥梁模式是一种相对简单的模式,通常以组合替代继承的方式实现。 从设计原则来讲,可以说是单一职责的一种体现。 将原本在一个类中的功能,按更细的粒度拆分到不同的类中,然后各自独立发展。 基本…...

【每日一题 | 2025年5.5 ~ 5.11】搜索相关题
个人主页:Guiat 归属专栏:每日一题 文章目录 1. 【5.5】P3717 [AHOI2017初中组] cover2. 【5.6】P1897 电梯里的尴尬3. 【5.7】P2689 东南西北4. 【5.8】P1145 约瑟夫5. 【5.9】P1088 [NOIP 2004 普及组] 火星人6. 【5.10】P1164 小A点菜7. 【5.11】P101…...
C语言速成之08循环语句全解析:从基础用法到高效实践
C语言循环语句全解析:从基础用法到高效实践 大家好,我是Feri,12年开发经验的程序员。循环语句是程序实现重复逻辑的核心工具,掌握while、do-while、for的特性与适用场景,能让代码更简洁高效。本文结合实战案例…...

多模态大语言模型arxiv论文略读(六十九)
Prompt-Aware Adapter: Towards Learning Adaptive Visual Tokens for Multimodal Large Language Models ➡️ 论文标题:Prompt-Aware Adapter: Towards Learning Adaptive Visual Tokens for Multimodal Large Language Models ➡️ 论文作者:Yue Zha…...
云计算-容器云-部署CICD-jenkins连接gitlab
安装 Jenkins 将Jenkins部署到default命名空间下。要求完成离线插件的安装,设置Jenkins的登录信息和授权策略。 上传BlueOcean.tar.gz包 [root@k8s-master-node1 ~]#tar -zxvf BlueOcean.tar.gz [root@k8s-master-node1 ~]#cd BlueOcean/images/ vim /etc/docker/daemon.json…...

精讲C++四大核心特性:内联函数加速原理、auto智能推导、范围for循环与空指针进阶
前引:在C语言长达三十余年的演进历程中,每一次标准更新都在试图平衡性能与抽象、控制与安全之间的微妙关系。从C11引入的"现代C"范式开始,开发者得以在保留底层控制能力的同时,借助语言特性大幅提升代码的可维护性与安全…...

【HarmonyOS 5】鸿蒙中常见的标题栏布局方案
【HarmonyOS 5】鸿蒙中常见的标题栏布局方案 一、问题背景: 鸿蒙中常见的标题栏:矩形区域,左边是返回按钮,右边是问号帮助按钮,中间是标题文字。 那有几种布局方式,分别怎么布局呢?常见的思维…...
Docker 部署 - Crawl4AI 文档 (v0.5.x)
Docker 部署 - Crawl4AI 文档 (v0.5.x) 快速入门 🚀 拉取并运行基础版本: # 不带安全性的基本运行 docker pull unclecode/crawl4ai:basic docker run -p 11235:11235 unclecode/crawl4ai:basic# 带有 API 安全性启用的运行 docker run -p 11235:1123…...
QuecPython+Aws:快速连接亚马逊 IoT 平台
提供一个可接入亚马逊 Iot 平台的客户端,用于管理亚马逊 MQTT 连接和影子设备。 初始化客户端 Aws class Aws(client_id,server,port,keep_alive,ssl,ssl_params)参数: client_id (str) - 客户端唯一标识。server (str) - 亚马逊 Iot 平台服务器地址…...
内存安全暗战:从 CVE-2025-21298 看 C 语言防御体系的范式革命
引言 2025 年 3 月,当某工业控制软件因 CVE-2025-21298 漏洞遭攻击,导致欧洲某能源枢纽的电力调度系统瘫痪 37 分钟时,全球网络安全社区再次被拉回 C 语言内存安全的核心战场。根据 CISA 年度报告,68% 的高危漏洞源于 C/C 代码&a…...
SpringCloud Gateway知识点整理和全局过滤器实现
predicate(断言): 判断uri是否符合规则 • 最常用的的就是PathPredicate,以下列子就是只有url中有user前缀的才能被gateway识别,否则它不会进行路由转发 routes:- id: ***# uri: lb://starry-sky-upmsuri: http://localhost:9003/predicate…...
全球实物文件粉碎服务市场洞察:合规驱动下的安全经济与绿色转型
一、引言:从纸质堆叠到数据安全的“最后一公里” 在数字化转型浪潮中,全球企业每年仍产生超过1.2万亿页纸质文件,其中包含大量机密数据、客户隐私及商业敏感信息。据QYResearch预测,2031年全球实物文件粉碎服务市场规模将达290.4…...
冒泡排序的原理
冒泡排序是一种简单的排序算法,它通过重复地遍历待排序的列表,比较相邻的元素并交换它们的位置来实现排序。具体原理如下: 冒泡排序的基本思想 冒泡排序的核心思想是通过相邻元素的比较和交换,将较大的元素逐步“冒泡”到列表的…...

Day22 Kaggle泰坦尼克号训练实战
作业 自行学习参考如何使用kaggle平台,写下使用注意点,并对下述比赛提交代码 kaggle泰坦里克号人员生还预测 一、流程 思路概述 数据加载 :读取泰坦尼克号的训练集和测试集。数据预处理 :处理缺失值、对分类变量进行编码、…...

深入浅出之STL源码分析7_模版实例化与全特化
1.引言 显示实例话和全特化的区别,之前我们在讨论类模版的时候,讨论过,他俩不是同一个概念,类模版中你如果全特化了,还是需要实例化才能生成代码,但是对于函数模版,这个是不同的,函…...
CAPL -实现SPRMIB功能验证
系列文章目录 抑制肯定响应消息指示位(SPRMIB) 二十一、CANdelaStudio深入-SPRMIB的配置 文章目录 系列文章目录一、SPRMIB是什么?二、SetSuppressResp(long flag)三、GetSuppressResp 一、SPRMIB是什么? 正响应:表示…...

2025 Mac常用软件安装配置
1、homebrew 2、jdk 1、使用brew安装jdk: brew install adoptopenjdk/openjdk/adoptopenjdk8 jdk默认安装位置在 /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home 目录。 2、配置环境变量: vim ~/.zshrc# Jdk export JAVA_HOM…...

容器技术 20 年:颠覆、重构与重塑软件世界的力量
目录 容器技术发展史 虚拟化技术向容器技术转变 Docker的横空出世 容器编排技术与Kubernetes 微服务的出现与Istio 工业标准的容器运行时 容器技术与 DevOps 的深度融合 无服务架构推波助澜 展望未来发展方向 从 20 世纪硬件虚拟化的笨重,到操作系统虚拟…...

cmake:test project
本文主要探讨cmake在测试和项目中的应用。 add_test add_test(NAME <name> COMMAND <command> [<arg>...] [CONFIGURATIONS <config>...] [WORKING_DIRECTORY <dir>] [COMMAND_EXPAND_LISTS]) add_test(NAME test_uni COMMAND $<TARGET_F…...

C++开发过程中的注意事项详解
目录 C++开发过程中的注意事项详解 一、内存管理:避免泄漏与资源浪费 1.1 使用智能指针管理动态内存 1.2 避免手动内存管理的陷阱 1.3 利用RAII机制管理资源 1.4 容器与内存分配 二、安全性:防御攻击与未定义行为 2.1 输入验证与安全编码 2.2 使用安全的通信协议 2…...
OpenWrt开发第8篇:树莓派开发板做无线接入点
文/指尖动听知识库-谷谷 文章为付费内容,商业行为,禁止私自转载及抄袭,违者必究!!! 文章专栏:Openwrt开发-基于Raspberry Pi 4B开发板 树莓派开发板作为无线接入点的时候,可以通过电脑和手机打开WiFi功能搜索到相应打开的WiFi; 1 通过Web操作界面开启wifi 1...
Leetcode (力扣)做题记录 hot100(42,104,226,101)
力扣第42题:接雨水 42. 接雨水 - 力扣(LeetCode) 左边遍历一次记录左侧最大值 右边同理,最后遍历一次 左侧右侧最小值减去当前值即可。 class Solution {public int trap(int[] height) {int n height.length;int[] leftMax …...

第六天:Java数组
数组 数组概述 数组是相同类型数据的有序集合。数组中的元素可以是任意数据类型,包括基本类型和引用类型数组描述是相同类型的若干个数据,按照一定的先后顺序排列组合而成。数组下标从0开始。 数组声明与创建 数组的声明 int[] nums;//声明一个数组…...

李沐动手深度学习(pycharm中运行笔记)——09.softmax回归+图像分类数据集+从零实现+简洁实现
09.softmax回归图像分类数据集从零实现简洁实现(与课程对应) 目录 一、softmax回归 1、回归 vs 分类 2、经典分类数据集: 3、从回归到分类——均方损失 4、从回归到多类分类——无校验比例 5、从回归到多类分类——校验比例 6、softmax和…...
Kubernetes生产实战(二十):容器大镜像拉取优化指南
在 Kubernetes 中优化大容器镜像的拉取速度,需要结合 镜像构建策略、集群网络架构 和 运行时配置 多方面进行优化。以下是分步解决方案: 一、镜像构建优化:减小镜像体积 1. 使用轻量级基础镜像 替换 ubuntu、centos 为 alpine、distroless …...