乐观锁

简介: 乐观锁是一种轻量级并发控制机制,假设数据通常不会被其他线程修改,因此在读取时不加锁,仅在更新时检查是否发生冲突。它通过版本号或CAS(Compare-and-Swap)实现,避免线程阻塞,提高并发性能,适用于读多写少的场景,如缓存、数据库更新等。相比悲观锁,乐观锁减少锁竞争,但频繁写冲突可能导致重试成本较高。

乐观锁(Optimistic Locking)是一种轻量级的并发控制机制,假设数据在大多数情况下不会被其他线程修改,因此不直接加锁,而是在更新时检查数据是否被篡改。相比传统的悲观锁(如synchronized),乐观锁避免了线程阻塞,提高了并发性能。

核心原理

  1. 无锁操作
    线程在读取数据时不加锁,直接进行业务操作,仅在更新数据时检查是否有其他线程修改过。

  2. 版本控制
    常见实现方式是为数据添加版本号(Version)时间戳(Timestamp)

    • 读取数据时记录版本号。
    • 更新时比较版本号,若一致则执行更新并递增版本号;否则失败重试。

乐观锁的优缺点

  • 优点

    • 高并发性能:无锁竞争,避免线程上下文切换。
    • 适用于读多写少场景:如缓存更新、报表统计等。
  • 缺点

    • 写冲突重试成本高:频繁写操作可能导致大量重试。
    • 不适合长事务:长时间持有数据可能增加冲突概率。

实现方式

1. CAS(Compare-and-Swap)

  • 原理:原子操作,比较内存中的值与预期值,若相同则更新为新值。
  • Java实现AtomicIntegerAtomicReference等原子类。
import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
   
    private AtomicInteger value = new AtomicInteger(0);

    public void increment() {
   
        int oldValue;
        int newValue;
        do {
   
            oldValue = value.get();
            newValue = oldValue + 1;
        } while (!value.compareAndSet(oldValue, newValue));
    }
}

2. 版本号机制

  • 数据库实现:表中添加version字段,更新时验证版本。

    UPDATE table 
    SET value = new_value, version = version + 1 
    WHERE id = ? AND version = ?;
    
  • Java实现

    public class VersionedData {
         
        private String data;
        private long version;
    
        public boolean update(String newData, long expectedVersion) {
         
            if (expectedVersion != this.version) {
         
                return false; // 版本冲突,更新失败
            }
            this.data = newData;
            this.version++;
            return true;
        }
    }
    

乐观锁的典型应用场景

  1. 数据库更新

    • 在SQL语句中通过WHERE version = ?实现行级锁。
  2. 缓存更新

    • 使用Compare-And-Set原子操作更新缓存值。
  3. 并发容器

    • ConcurrentHashMapcomputeIfAbsent()等方法使用乐观锁思想。
  4. 分布式系统

    • 通过分布式版本号或时间戳实现最终一致性。

与悲观锁的对比

特性 乐观锁 悲观锁
加锁时机 仅在更新时检查冲突 读取时加锁
实现方式 CAS、版本号 synchronizedReentrantLock
适用场景 读多写少、冲突少 写多冲突多、长事务
线程状态 无阻塞,冲突时重试 阻塞等待锁释放
典型案例 数据库乐观锁、Atomic类 同步方法、数据库行锁

注意事项

  1. ABA问题

    • 问题:值从A→B→A,版本号不变,CAS误认为未修改。
    • 解决方案:使用AtomicStampedReference记录版本戳。
  2. 重试机制

    • 冲突频繁时,需设置最大重试次数或退化为悲观锁。
  3. 长事务风险

    • 长时间持有数据可能导致冲突概率升高,建议拆分事务。

Java中的乐观锁工具

  1. 原子类(JUC)

    • AtomicIntegerAtomicLongAtomicReference等。
  2. StampedLock

    • JDK 8引入,支持乐观读锁,适用于读多写少场景。
    import java.util.concurrent.locks.StampedLock;
    
    public class StampedLockExample {
         
        private final StampedLock lock = new StampedLock();
        private int value = 0;
    
        public int read() {
         
            long stamp = lock.tryOptimisticRead(); // 获取乐观读戳记
            int currentValue = value;
            if (!lock.validate(stamp)) {
          // 检查戳记是否有效(是否有写操作)
                stamp = lock.readLock(); // 升级为悲观读锁
                try {
         
                    currentValue = value;
                } finally {
         
                    lock.unlockRead(stamp);
                }
            }
            return currentValue;
        }
    }
    

总结

乐观锁通过假设无冲突来减少锁的使用,在高并发读场景中表现优异。它是数据库、缓存和分布式系统中实现高性能并发控制的关键技术。但需注意冲突处理和ABA问题,合理选择乐观锁与悲观锁的使用场景,才能发挥最佳性能。

目录
相关文章
|
5月前
|
缓存 Java
自旋锁
自旋锁是一种轻量级同步机制,适用于多线程环境。其核心思想是线程在获取锁失败时不阻塞,而是通过忙等待(自旋)不断尝试获取锁,从而避免上下文切换的开销。常见实现依赖CAS原子操作,适用于锁持有时间短、并发度高的场景,如计数器更新或缓存操作。但长时间自旋会浪费CPU资源,因此更适合多核环境下使用。Java中可通过`AtomicBoolean`实现简单自旋锁,JVM也对其进行了自适应优化。合理使用可提升性能,但需注意控制自旋时间和竞争粒度。
204 0
|
5月前
|
监控 安全 Java
悲观锁
悲观锁是一种并发控制机制,假设数据在访问时易被修改,故在操作前加锁以确保线程安全。其优点为强一致性与实现简单,但性能开销大、易阻塞,适用于写多、一致性要求高的场景,如金融交易。常见实现包括数据库行锁、表锁及Java中的`synchronized`与`ReentrantLock`。
108 0
|
5月前
|
监控 Java 关系型数据库
排他锁
排他锁(写锁)是一种互斥机制,确保同一时间仅一个线程访问共享资源,保障数据一致性与完整性。适用于写操作场景,如更新、删除等,常见于数据库和多线程编程。其优点为强一致性和实现简单,但并发度低且存在死锁风险。可通过synchronized、ReentrantLock等方式实现。
124 0
|
7月前
|
存储 Kubernetes Serverless
容器技术 20 年:颠覆、重构与重塑软件世界的力量
从 20 世纪硬件虚拟化的笨重,到操作系统虚拟化的轻量探索,容器技术历经蜕变。2013 年 Docker 横空出世,以 “一次构建,到处运行” 的创举打破环境壁垒,开启容器黄金时代。随后,Docker Compose、Kubernetes、Istio 等技术相继涌现,从多容器管理到集群编排,再到微服务治理,不断突破应用部署与运维的边界。如今,容器与 DevOps 深度融合,Serverless 架构异军突起,共同重塑软件开发生态。本文将带你穿越容器技术发展的关键节点,揭秘其如何以颠覆性力量推动云计算与数字化。
526 64
|
机器学习/深度学习 人工智能 缓存
基于AIGC的自动化内容生成与应用
基于AIGC的自动化内容生成与应用
577 3
|
缓存 NoSQL Redis
Redis命令:列表模糊删除详解
Redis命令:列表模糊删除详解
447 3
|
机器学习/深度学习 存储 自然语言处理
大语言模型参数真的必须要万亿以上吗?
本文探讨了大语言模型(LLMs)的发展及其在自然语言处理领域的应用。随着模型规模的不断增大,文章分析了参数规模与性能之间的关系,并展示了不同规模模型的优势与挑战。此外,文中还提供了代码示例,介绍了参数设置的方法。未来研究方向包括模型压缩和多模态学习,以进一步优化模型性能。总之,选择合适的模型规模对于平衡性能和效率至关重要。
|
NoSQL Redis 数据库
|
Web App开发 JavaScript 前端开发
Web 页面性能衡量指标-以用户为中心的性能指标
Web 页面性能衡量指标-以用户为中心的性能指标 以用户为中心的性能指标是理解和改进站点体验的关键点 一、以用户为中心的性能指标 1. 指标是用来干啥的? 指标是用来衡量性能和用户体验的 2. 指标类型 • 感知加载速度:网页可以多快地加载网页中的所有视觉元素并将其渲染到屏幕上 • 加载响应速度:页面加载和执行组件快速响应用户互动所需的 JavaScript 代码的速度 • 运行时响应速度:网页在加载后对用户互动的响应速度 • 视觉稳定性:页面上的元素是否会以用户意想不到的方式发生偏移,是否可能会干扰用户的互动? • 流畅性:过渡和动画是否以一致的帧速率渲染,并在一种状态之间流畅地流动
607 1

热门文章

最新文章