StampedLock入门教程

文章目录

      • 一、理解“戳” (Stamp)
      • 二、为什么 `StampedLock` 能提高读性能?秘密在于“乐观读”
      • StampedLock性能对比
        • 性能对比结果图
      • 总结
  • StampedLock完整演示代码
  • 对代码的疑问之处
      • 问题一:为什么 `demonstrateOptimisticReadFailure` 中写线程能修改成功?
      • 问题二:锁升级这块不是很理解,为什么结果是成功的?
        • 1. 什么是锁升级?为什么需要它?
        • 2. 锁升级什么时候会成功?什么时候会失败?
        • 3. 为什么您的代码里升级成功了?
        • 4. `finally` 块中的 `lock.unlock(stamp)`
  • 🎯总结

直击了 StampedLock 的设计核心。解释它提升读性能的秘密。


一、理解“戳” (Stamp)

首先,我们来理解“戳”(Stamp)是什么。

ReentrantReadWriteLock 中,lock()unlock() 是没有参数和返回值的。你只要调用 lock() 获取锁,用完后调用 unlock() 释放锁即可。

StampedLock 完全不同。它的所有“上锁”操作都会返回一个long类型的数字,这个数字就是所谓的**“戳” (Stamp)。而它所有的“解锁”操作,都必须传入这个“戳”**。

long stamp = lock.writeLock(); // 上写锁,返回一个戳
try {// ...
} finally {lock.unlockWrite(stamp); // 解锁时必须传入获取锁时得到的那个戳
}

为什么需要“戳”?
这个“戳”本质上是一个版本号或者状态快照

  • 当没有任何锁时,它是一个初始值。
  • 当有线程获取写锁时,这个“戳”的值会发生改变(比如增加一个版本号)。
  • 每次上锁操作返回的“戳”都是独一无二的。

所以,这句话 “在使用读锁、写锁时都必须配合【戳】使用”,指的就是这种 lock() 返回戳、unlock() 传入戳的使用模式。这个“戳”是锁状态的凭证。

特别强调:获取写锁的时候,版本号才会改变!!如果我们获取读锁,是不会修改版本号的!!!!


二、为什么 StampedLock 能提高读性能?秘密在于“乐观读”

ReentrantReadWriteLock 无论如何,都是一种悲观锁。即使是读锁,当多个读线程和写线程竞争时,仍然需要排队、阻塞、上下文切换,这些都有性能开销。它总是悲观地认为“我读的时候,很可能会有别人来写”。

StampedLock 之所以性能更高,是因为它引入了一种全新的、ReentrantReadWriteLock 没有的模式——乐观读 (Optimistic Reading)

乐观读的核心思想是:我非常乐观地认为,在我读取共享数据期间,根本不会有线程来修改它。

基于这个乐观的假设,StampedLock 的乐观读操作如下:

  1. 尝试乐观读 (tryOptimisticRead):

    • 线程想读取共享数据,它先调用 lock.tryOptimisticRead()
    • 这个方法不会加任何锁,不会阻塞线程,它只是瞬间获取一下当前的“戳”(版本号),然后立即返回。这个过程几乎没有开销,速度极快。
  2. 读取共享数据:

    • 线程拿着这个“戳”,然后去读取共享变量(比如 x, y 的值)。
  3. 验证“戳” (validate):

    • 读完数据后,线程必须调用 lock.validate(stamp),并传入第一步获取的那个“戳”。
    • validate 方法会检查从第一步到当前时刻,有没有写操作发生过。它的判断依据就是**“戳”的版本号有没有变**。
      • 如果版本号没变 (validate返回true):这说明在刚才的读取期间,没有任何写操作来干扰。那么我们刚才读取的数据就是一致的、有效的。这次“乐观读”成功了!
      • 如果版本号变了 (validate返回false):这说明在我们读取数据的过程中,有一个“写线程”插了进来,获取了写锁,并修改了数据。那么我们刚才读到的数据就是“脏”的、不可信的。
  4. 失败后的补偿:

    • 如果 validate 失败了,说明乐观失败了,我们不能再这么乐观。
    • 此时,程序必须“升级”为悲观的读锁,即调用 lock.readLock() 来老老实实地加锁,然后重新读取一遍数据。

性能提升的关键点:
读多写少的场景下,绝大多数的乐观读操作都会成功。成功的乐观读,其开销仅仅是两次方法调用和一次版本号比较,完全没有线程阻塞和上下文切换的开销,甚至没有CAS操作的开销,性能几乎和无锁操作一样快。

只有在极少数情况下(读的过程中发生了写),乐观读才会失败,并升级为悲观读锁,付出一点额外代价。但总体算下来,性能提升是巨大的。


StampedLock性能对比

package StampLock;import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.StampedLock;public class StampedLockPerformanceDemo {// 共享数据static class SharedData {private int value = 0;// 三种不同的锁private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();private final StampedLock stampedLock = new StampedLock();// 1. 使用 ReentrantReadWriteLock 读取public int readWithRWLock() {rwLock.readLock().lock();try {return value;} finally {rwLock.readLock().unlock();}}// 2. 使用 StampedLock 的悲观读public int readWithStampedLock() {long stamp = stampedLock.readLock();try {return value;} finally {stampedLock.unlockRead(stamp);}}// 3. 使用 StampedLock 的乐观读(性能最好!)public int readWithOptimisticRead() {// 获取乐观读戳long stamp = stampedLock.tryOptimisticRead();// 读取数据(无锁!)int currentValue = value;// 验证期间是否有写操作if (!stampedLock.validate(stamp)) {// 升级为悲观读stamp = stampedLock.readLock();try {currentValue = value;} finally {stampedLock.unlockRead(stamp);}}return currentValue;}// 写操作(使用 StampedLock)public void write(int newValue) {long stamp = stampedLock.writeLock();try {value = newValue;} finally {stampedLock.unlockWrite(stamp);}}}public static void main(String[] args) throws InterruptedException {SharedData data = new SharedData();int threadCount = 100;int iterations = 100000;// 测试不同读锁的性能System.out.println("开始性能测试...\n");// 1. 测试 ReentrantReadWriteLocklong startTime = System.currentTimeMillis();testReadPerformance(data, threadCount, iterations, "RWLock");long rwLockTime = System.currentTimeMillis() - startTime;System.out.println("ReentrantReadWriteLock 耗时: " + rwLockTime + " ms");// 2. 测试 StampedLock 悲观读startTime = System.currentTimeMillis();testReadPerformance(data, threadCount, iterations, "StampedLock");long stampedLockTime = System.currentTimeMillis() - startTime;System.out.println("StampedLock 悲观读 耗时: " + stampedLockTime + " ms");// 3. 测试 StampedLock 乐观读startTime = System.currentTimeMillis();testReadPerformance(data, threadCount, iterations, "OptimisticRead");long optimisticTime = System.currentTimeMillis() - startTime;System.out.println("StampedLock 乐观读 耗时: " + optimisticTime + " ms");System.out.println("\n性能提升: " +String.format("%.2f", (double)rwLockTime / optimisticTime) + " 倍");}private static void testReadPerformance(SharedData data, int threadCount,int iterations, String lockType)throws InterruptedException {CountDownLatch latch = new CountDownLatch(threadCount);for (int i = 0; i < threadCount; i++) {new Thread(() -> {for (int j = 0; j < iterations; j++) {switch (lockType) {case "RWLock":data.readWithRWLock();break;case "StampedLock":data.readWithStampedLock();break;case "OptimisticRead":data.readWithOptimisticRead();break;}}latch.countDown();}).start();}latch.await();}
}

无锁的乐观读对性能提升极其明显!提升足足150倍!!!但是呢,StampedLock也不是万能的,他只是首先尝试使用乐观读(无锁),如果发现版本号不对劲,中间被修改过了,就需要加锁,重新读取,丢掉脏数据!!
关键代码:

// 3. 使用 StampedLock 的乐观读(性能最好!)public int readWithOptimisticRead() {// 获取乐观读戳long stamp = stampedLock.tryOptimisticRead();// 读取数据(无锁!)int currentValue = value;// 验证期间是否有写操作if (!stampedLock.validate(stamp)) {// 升级为悲观读stamp = stampedLock.readLock();try {currentValue = value;} finally {stampedLock.unlockRead(stamp);}}return currentValue;}
性能对比结果图

在这里插入图片描述

总结

  • “配合【戳】使用”:指的是StampedLock所有上锁/解锁操作都围绕一个long类型的版本号(戳)来进行。
  • 提高读性能的原因StampedLock引入了乐观读机制。在“读多写少”的场景下,乐观读允许线程在不加锁的情况下读取数据,并通过“戳”来验证数据的一致性。这个过程避免了绝大多数读操作的加锁、阻塞和线程切换开销,从而极大地提升了读取性能。它是用一种“先上车后补票”的乐观策略换来了性能的飞跃。

StampedLock完整演示代码

package StampLock;import java.util.concurrent.locks.StampedLock;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.StampedLock;
import java.util.Random;
import java.util.concurrent.locks.StampedLock;
public class SimpleStampedLockDemo {private static class Point {private double x, y;private final StampedLock lock = new StampedLock();// 移动点的位置(写操作)public void move(double deltaX, double deltaY) {long stamp = lock.writeLock();try {x += deltaX;y += deltaY;System.out.println(Thread.currentThread().getName() +" 移动点到: (" + x + ", " + y + ")");} finally {lock.unlockWrite(stamp);}}// 计算到原点的距离(乐观读)- 修正版public double distanceFromOrigin() {// 1. 尝试乐观读long stamp = lock.tryOptimisticRead();// 2. 读取数据double currentX = x;double currentY = y;// 模拟读取过程需要一些时间(让写线程有机会介入)try {Thread.sleep(100);  // 模拟复杂计算} catch (InterruptedException e) {e.printStackTrace();}// 3. 验证在读取过程中数据是否被修改if (!lock.validate(stamp)) {// 数据被修改了,需要加锁重新读取System.out.println(Thread.currentThread().getName() +" 乐观读失败,升级为悲观读");stamp = lock.readLock();try {currentX = x;currentY = y;} finally {lock.unlockRead(stamp);}} else {System.out.println(Thread.currentThread().getName() +" 乐观读成功!");}return Math.sqrt(currentX * currentX + currentY * currentY);}// 专门用于演示乐观读失败的方法public void demonstrateOptimisticReadFailure() {System.out.println("开始演示乐观读失败场景...");// 读线程Thread reader = new Thread(() -> {long stamp = lock.tryOptimisticRead();System.out.println(Thread.currentThread().getName() +" 获取乐观读戳: " + stamp);// 读取第一个值double currentX = x;System.out.println(Thread.currentThread().getName() +" 读取 x = " + currentX);// 故意等待,让写线程有机会修改数据try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}// 读取第二个值double currentY = y;System.out.println(Thread.currentThread().getName() +" 读取 y = " + currentY);// 验证if (!lock.validate(stamp)) {System.out.println(Thread.currentThread().getName() +" ❌ 乐观读失败!数据在读取过程中被修改了");// 重新用悲观读stamp = lock.readLock();try {currentX = x;currentY = y;System.out.println(Thread.currentThread().getName() +" 使用悲观读重新读取: (" + currentX + ", " + currentY + ")");} finally {lock.unlockRead(stamp);}} else {System.out.println(Thread.currentThread().getName() +" ✓ 乐观读成功");}}, "读线程");// 写线程Thread writer = new Thread(() -> {try {// 等待读线程开始Thread.sleep(100);System.out.println(Thread.currentThread().getName() +" 准备修改数据...");move(10, 10);} catch (InterruptedException e) {e.printStackTrace();}}, "写线程");try {reader.start();writer.start();reader.join();writer.join();} catch (InterruptedException e) {e.printStackTrace();}}// 锁升级示例public void moveToOriginIfInFirstQuadrant() {long stamp = lock.readLock();try {if (x > 0 && y > 0) {// 尝试将读锁升级为写锁long writeStamp = lock.tryConvertToWriteLock(stamp);if (writeStamp != 0) {stamp = writeStamp;System.out.println(Thread.currentThread().getName() +" ✓ 成功将读锁升级为写锁");x = 0;y = 0;} else {System.out.println(Thread.currentThread().getName() +" ❌ 读锁升级失败,重新获取写锁");lock.unlockRead(stamp);stamp = lock.writeLock();try {x = 0;y = 0;} finally {lock.unlockWrite(stamp);return;}}}} finally {lock.unlock(stamp);}}}public static void main(String[] args) throws InterruptedException {Point point = new Point();System.out.println("=== StampedLock 简单示例 ===\n");// 示例1:基本的读写操作System.out.println("1. 基本读写操作:");point.move(3, 4);System.out.println("距离原点: " + point.distanceFromOrigin());System.out.println();// 示例2:单线程乐观读(肯定成功)System.out.println("2. 单线程乐观读(肯定成功):");double distance = point.distanceFromOrigin();System.out.println("距离: " + distance);System.out.println();// 示例3:演示乐观读失败System.out.println("3. 并发场景下的乐观读失败:");point.demonstrateOptimisticReadFailure();System.out.println();// 示例4:演示锁升级System.out.println("4. 锁升级示例:");point.move(5, 5);  // 确保在第一象限Thread upgrader = new Thread(() -> {point.moveToOriginIfInFirstQuadrant();}, "升级线程");upgrader.start();upgrader.join();System.out.println("\n最终位置: (" + point.x + ", " + point.y + ")");}
}

在这里插入图片描述

对代码的疑问之处

当时我对控制台打印的疑问是:
sleep不释放锁,为什么reader在sleep了200ms之后,虽然Writer还有100ms的时间可以获取锁,但是reader不释放锁啊,为什么Writer还是能获取到锁??
其实我是理解错误了!在乐观读模式下,读线程在调用sleep时,根本没有持有任何锁!

我们来逐一详细拆解。


问题一:为什么 demonstrateOptimisticReadFailure 中写线程能修改成功?

您对线程执行顺序的理解出现了一点偏差,这也是并发编程初学者最容易遇到的一个困惑点。您可能是这样想的:

  • 您的设想(串行思路):读线程启动 -> 读x -> 睡200ms -> 读y -> 结束。然后写线程启动 -> 睡100ms -> 修改。

但实际情况是,t.start() 只是告诉操作系统“这个线程可以开始运行了”,但具体什么时候运行、运行多长时间,都由 CPU的线程调度器来决定。两个线程一旦 start(),就可以看作是在同时、并行地执行。

我们来梳理一下实际的事件时间线

  1. T=0ms: main线程调用了 reader.start()writer.start()。此时,读线程和写线程都进入了“就绪”状态,随时可以被CPU执行。

  2. T=~1ms (举例): 读线程抢到了CPU时间片。

    • 它执行 lock.tryOptimisticRead(),获取了版本号(比如512)。
    • 它读取了 x 的值(3.0)。
    • 然后它调用 Thread.sleep(200)主动放弃CPU,进入了休眠状态。它要等200毫秒后才能醒来。
  3. T=~2ms: 写线程抢到了CPU时间片。

    • 它调用 Thread.sleep(100),也主动放弃CPU,进入休眠。它只需要等100毫秒。
  4. T=~102ms: 写线程的100ms睡眠时间结束了!

    • 它被唤醒,重新进入“就绪”状态,并很快抢到CPU。
    • 它调用 move(10, 10),成功获取了写锁(因为此时没有其他锁),将 xy 修改为 (13.0, 14.0)。
    • 写线程的工作完成了。
  5. T=~201ms: 读线程的200ms睡眠时间现在才结束!

    • 它被唤醒,从 sleep(200) 的下一行代码继续执行。
    • 它开始读取 y 的值。但此时的 y 已经是被写线程修改后的 14.0
    • 它读取完毕后,调用 lock.validate(512)
    • StampedLock 发现,从它获取版本号512到现在,中间发生了一次写操作(版本号已经变了)。
    • 因此 validate 返回 false,乐观读失败。

结论:代码完美地达到了演示失败的目的。正是因为写线程的睡眠时间(100ms)比读线程的睡眠时间(200ms)短,所以写操作总能发生在读操作的“读取x”和“读取y”这两个动作之间,从而导致乐观读验证失败。


问题二:锁升级这块不是很理解,为什么结果是成功的?

我们来深入理解一下 tryConvertToWriteLock 这个“锁升级”操作。

1. 什么是锁升级?为什么需要它?

想象一个场景:你需要先读取一个共享数据,根据数据的值,再决定是否要修改它。

  • 常规的笨办法

    1. 先加读锁
    2. 读取数据,发现需要修改。
    3. 释放读锁
    4. 再加写锁
    5. (问题来了) 在你释放读锁和获取写锁的这个“空档期”,很可能有另一个线程冲进来修改了数据,那你刚才的判断就白费了,你必须重新读取和判断,非常麻烦。
  • 锁升级的聪明办法
    tryConvertToWriteLock 提供了一个在持有读锁的情况下,直接尝试转变为写锁的机会,中间不释放任何锁,从而避免了上述的“空档期”问题。这是一种优化。

2. 锁升级什么时候会成功?什么时候会失败?

tryConvertToWriteLock 是一个“乐观”的尝试,它成功的条件非常苛刻

  • 成功条件:当尝试升级时,当前线程必须是唯一的读者。也就是说,不能有任何其他线程持有读锁。如果StampedLock发现只有你这一个读者,它就会很顺利地把你的读锁“升级”成写锁,并返回一个新的、代表写锁的“戳”。
  • 失败条件:只要当时还有任何一个其他线程也持有读锁,升级就会立即失败,并返回 0。这是为了防止死锁(如果两个读线程都想升级成写锁,它们会相互等待对方释放读锁,从而死锁)。
3. 为什么您的代码里升级成功了?

我们看一下您的 main 函数中调用这部分的代码:

// 示例4:演示锁升级
System.out.println("4. 锁升级示例:");
point.move(5, 5);  // 确保在第一象限
Thread upgrader = new Thread(() -> {point.moveToOriginIfInFirstQuadrant();
}, "升级线程");
upgrader.start();
upgrader.join();

在这里,您只创建了一个名为“升级线程”的线程去执行 moveToOriginIfInFirstQuadrant 这个方法。

所以,当这个线程执行到 lock.tryConvertToWriteLock(stamp) 时,它自己是当前唯一的读者,没有任何其他线程持有读锁。因此,它完全满足了升级成功的苛刻条件,所以升级必然成功,并打印出 ✓ 成功将读锁升级为写锁

4. finally 块中的 lock.unlock(stamp)

您可能会注意到,finally 块里只有一个 lock.unlock(stamp),它是如何知道该解锁读锁还是写锁的呢?

这也是StampedLock的一个巧妙之处。unlock(stamp) 方法会根据传入的“戳”的类型,来自动判断是该执行 unlockRead 还是 unlockWrite

  • 如果升级失败,stamp 变量里保存的还是最初的读锁戳unlock(stamp) 就执行读锁释放。
  • 如果升级成功,代码 stamp = writeStamp; 会把写锁戳赋给 stamp 变量,unlock(stamp) 就执行写锁释放。

这种设计简化了 finally 块的逻辑。

🎯总结

  1. 为什么叫"戳"(Stamp)?

    • 每次获取锁都会返回一个唯一的数字(戳)
    • 释放锁时必须提供对应的戳
    • 就像票据系统,确保锁的正确配对
  2. 为什么能提高读性能?

    • 乐观读:不加锁,直接读取,性能最高
    • 只在数据被修改时才升级为真正的锁
    • 适合读多写少的场景
  3. 使用场景

    • 读操作远多于写操作
    • 对读性能要求很高
    • 可以容忍偶尔的读重试
  4. 注意事项

    • 不支持重入
    • 必须正确管理戳
    • 不支持条件变量(Condition)

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

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

相关文章

基于云计算的振动弦分析:谐波可视化与波动方程参数理解-AI云计算数值分析和代码验证

振动弦方程是一个基础的偏微分方程&#xff0c;它描述了弹性弦的横向振动。其应用范围广泛&#xff0c;不仅可用于模拟乐器和一般的波动现象&#xff0c;更是数学物理以及深奥的弦理论中的重要基石。 ☁️AI云计算数值分析和代码验证 振动弦方程是描述固定两端弹性弦横向振动的…

Qt .pro配置gcc相关命令(三):-W1、-L、-rpath和-rpath-link

目录 1.Linux 动态库相关知识 1.1.动态库查找路径 1.2.查看程序依赖的动态库 1.3.修改动态库查找路径的方法 1.4.动态链接器缓存管理 2.-Wl参数 3.-L选项&#xff08;编译时路径&#xff09; 4.-rpath参数(运行时路径) 5.-rpath-link 参数 6.常见问题与解决方案 7.总…

Hoppscotch

官方地址 xixiaxiazxiaxix下载 • Hoppscotch Hoppscotch 是一款轻量级、基于 Web 的 API 开发套件&#xff0c;其核心功能和特点如下&#xff1a; 核心功能3 交互式 API 测试&#xff1a;允许用户实时发送请求并查看响应&#xff0c;方便记录 API 行为&#xff0c;在记录响…

RabbitMQ 知识详解(Java版)

RabbitMQ 知识详解&#xff08;Java版&#xff09; RabbitMQ 是一个开源的消息代理&#xff0c;实现了高级消息队列协议&#xff08;AMQP&#xff09;。它用于在分布式系统中实现应用解耦、异步通信和流量削峰。 核心概念 生产者(Producer)&#xff1a;发送消息的应用消费者(…

Flink task、Operator 和 UDF 之间的关系

要真正驾驭 Flink 并构建出高效、稳定、可扩展的流处理应用&#xff0c;仅仅停留在 API 的表面使用是远远不够的。深入理解其内部的运行机制&#xff0c;洞悉数据从代码到分布式执行的完整生命周期&#xff0c;以及明晰各个核心组件之间错综复杂而又协同工作的关系&#xff0c;…

Veeam Backup Replication系统的安装与使用

Veeam Backup & Replication系统安装与使用 系统简介 核心功能 备份与恢复&#xff1a;专注于虚拟化环境&#xff08;如VMware和Hyper-V&#xff09;的备份与恢复&#xff0c;支持物理服务器和云环境。快速恢复&#xff1a;提供即时恢复功能&#xff0c;可在几分钟内恢复…

十四、【ESP32全栈开发指南:搭建轻量级HTTP服务器】

一、HTTP协议基础 HTTP&#xff08;Hyper Text Transfer Protocol&#xff09;作为互联网基础协议&#xff0c;采用请求-响应模型工作&#xff1a; 1.1 HTTP请求组成 GET /uri?query1value1 HTTP/1.1 // 请求行&#xff08;方法URI协议版本&#xff09; Host: example…

java中LinkedList和ArrayList的区别和联系?

我们被要求解释Java中LinkedList和ArrayList的区别和联系。下面将分别从实现原理、性能特点、使用场景等方面进行详细说明&#xff0c;并总结它们的相同点和不同点。 # 一、联系&#xff08;共同点&#xff09; 1. 都实现了List接口&#xff0c;因此具有List接口的所有方法&…

明远智睿SD2351核心板:边缘计算时代的工业级核心引擎深度解析

在工业4.0与物联网深度融合的背景下&#xff0c;边缘计算设备正从单一功能模块向高集成度、智能化平台演进。明远智睿推出的SD2351核心板&#xff0c;凭借其异构计算架构、工业级接口资源和全栈技术生态&#xff0c;重新定义了边缘计算设备的性能边界。本文将从技术架构、场景适…

Flask 动态模块注册

目录 1. 项目概述2. 项目结构3. 核心组件解析3.1 动态模块注册系统 (api/__init__.py)3.2 应用程序入口 (setup_demo.py) 4. 模块开发指南4.1 标准模块 (*_app.py)4.2 SDK模块 (sdk/*.py) 5. URL路径规则6. 如何使用6.1 启动应用6.2 添加新模块 7. 工作原理 1. 项目概述 这个项…

JVM 内存、JMM内存与集群机器节点内存的联系

目录 1、JVM 内存 1.1、分配机制 1.2、jvm模型位置 1.3、字节码内存块 2、JMM内存 2.1、JMM模型 2.2、工作流程图 1、工作内存与主内存的交互 2. 多线程下的主内存与堆内存交互 2.3、 主内存与工作内存的同步方案 1、volatile 2、synchronized 3、final 3、内存使…

学习昇腾开发的第一天--环境配置

1、昇腾社区官网&#xff1a;昇腾社区官网-昇腾万里 让智能无所不及 2、产品-->选择开发者套件-->点击制卡工具的下载&#xff1a;资源-Atlas 200I DK A2-昇腾社区 3、如果制卡工具不能使用在线制卡&#xff0c;可以下载镜像到本地使用本地制卡&#xff1a;Linux系统制…

Android WebView 深色模式适配方案总结

Android WebView 深色模式适配方案总结 在 Android WebView 中适配深色模式&#xff08;Dark Mode&#xff09;是一个常见的需求&#xff0c;尤其是当加载的网页没有原生支持 prefers-color-scheme 时。本文将介绍 3 种主流方案&#xff0c;并分析它们的优缺点&#xff0c;帮助…

项目练习:使用mybatis的foreach标签,实现union all的拼接语句

文章目录 一、需求说明二、需求分析三、代码实现四、报表效果 一、需求说明 在sql查询数据后&#xff0c;对数据分组统计。并最后进行总计。 二、需求分析 最终&#xff0c;我想用sql来实现这个统计和查询的功能。 那么&#xff0c;怎么又查询&#xff0c;又统计了&#xf…

7.7 Extracting and saving responses

Chapter 7-Fine-tuning to follow instructions 7.7 Extracting and saving responses 在本节中&#xff0c;我们保存测试集响应以便在下一节中评分&#xff0c;除此之外保存模型的副本以供将来使用。 ​ 首先&#xff0c;让我们简单看看finetuned模型生成的响应 torch.manu…

计算机网络第3章(上):数据链路层全解析——组帧、差错控制与信道效率

目录 一、数据链路层的功能二、组帧2.1 字符计数法&#xff08;Character Count&#xff09;2.2 字符填充法&#xff08;Character Stuffing&#xff09;2.3 零比特填充法2.4 违规编码法 三、差错控制3.1 检错编码&#xff08;奇偶校验码&#xff09;3.2 循环冗余校验&#xff…

铸铁试验平台的重要性及应用前景

铸铁作为一种重要的金属材料&#xff0c;在工业生产中扮演着举足轻重的角色。为了确保铸铁制品的质量和性能&#xff0c;铸铁材料的试验是必不可少的环节。而铸铁试验平台则是进行铸铁试验的关键设备之一&#xff0c;它为铸铁材料的研究和开发提供了重要的技术支持。本文将探讨…

std::shared_ptr引起内存泄漏的例子

目录 一、循环引用&#xff08;最常见场景&#xff09; 示例代码 内存泄漏原因 二、共享指针管理的对象包含自身的 shared_ptr 示例代码 内存泄漏&#xff08;或双重释放&#xff09;原因 三、解决方案 1. 循环引用&#xff1a;使用 std::weak_ptr 2. 对象获取自身的 …

AI 知识数据库搭建方案:从需求分析到落地实施

AI 知识数据库的搭建需结合业务场景、数据特性与技术架构&#xff0c;形成系统化解决方案。以下是一套完整的搭建框架&#xff0c;涵盖规划、设计、实施及优化全流程&#xff1a; 一、前期规划&#xff1a;需求分析与目标定义 1. 明确业务场景与知识需求 场景导向&#xff1a…

Tensorflow 基础知识:变量、常量、占位符、Session 详解

在深度学习领域,TensorFlow 是一个广泛使用的开源机器学习框架。想要熟练使用 TensorFlow 进行模型开发,掌握变量、常量、占位符和 Session 这些基础知识是必不可少的。接下来,我们就深入了解一下它们的概念、用处,并通过代码示例进行演示。 一、常量(Constant) 常量,顾…