乐观锁

简介: 乐观锁是一种轻量级并发控制机制,假设数据通常不会被其他线程修改,因此在读取时不加锁,仅在更新时检查是否发生冲突。它通过版本号或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问题,合理选择乐观锁与悲观锁的使用场景,才能发挥最佳性能。

目录
相关文章
|
8月前
|
监控 Java 关系型数据库
排他锁
排他锁(写锁)是一种互斥机制,确保同一时间仅一个线程访问共享资源,保障数据一致性与完整性。适用于写操作场景,如更新、删除等,常见于数据库和多线程编程。其优点为强一致性和实现简单,但并发度低且存在死锁风险。可通过synchronized、ReentrantLock等方式实现。
220 0
|
8月前
|
缓存 Java
自旋锁
自旋锁是一种轻量级同步机制,适用于多线程环境。其核心思想是线程在获取锁失败时不阻塞,而是通过忙等待(自旋)不断尝试获取锁,从而避免上下文切换的开销。常见实现依赖CAS原子操作,适用于锁持有时间短、并发度高的场景,如计数器更新或缓存操作。但长时间自旋会浪费CPU资源,因此更适合多核环境下使用。Java中可通过`AtomicBoolean`实现简单自旋锁,JVM也对其进行了自适应优化。合理使用可提升性能,但需注意控制自旋时间和竞争粒度。
320 0
|
8月前
|
监控 安全 Java
悲观锁
悲观锁是一种并发控制机制,假设数据在访问时易被修改,故在操作前加锁以确保线程安全。其优点为强一致性与实现简单,但性能开销大、易阻塞,适用于写多、一致性要求高的场景,如金融交易。常见实现包括数据库行锁、表锁及Java中的`synchronized`与`ReentrantLock`。
143 0
|
8月前
|
数据安全/隐私保护
解释对称加密、非对称加密、哈希摘要
加密技术分为对称加密与非对称加密。对称加密使用同一密钥进行加解密,速度快但需严保管密钥;非对称加密则用公钥加密、私钥解密,安全性高但速度较慢。哈希摘要用于验证数据完整性,代表原始数据特征。
231 0
|
10月前
|
存储 Kubernetes Serverless
容器技术 20 年:颠覆、重构与重塑软件世界的力量
从 20 世纪硬件虚拟化的笨重,到操作系统虚拟化的轻量探索,容器技术历经蜕变。2013 年 Docker 横空出世,以 “一次构建,到处运行” 的创举打破环境壁垒,开启容器黄金时代。随后,Docker Compose、Kubernetes、Istio 等技术相继涌现,从多容器管理到集群编排,再到微服务治理,不断突破应用部署与运维的边界。如今,容器与 DevOps 深度融合,Serverless 架构异军突起,共同重塑软件开发生态。本文将带你穿越容器技术发展的关键节点,揭秘其如何以颠覆性力量推动云计算与数字化。
655 64
阿里面试:每天新增100w订单,如何的分库分表?这份答案让我当场拿了offer
例如,在一个有 10 个节点的系统中,增加一个新节点,只会影响到该新节点在哈希环上相邻的部分数据,其他大部分数据仍然可以保持在原节点,大大减少了数据迁移的工作量和对系统的影响。狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由”。在 3 - 5 年的中期阶段,随着业务的稳定发展和市场份额的进一步扩大,订单数据的增长速度可能会有所放缓,但仍然会保持在每年 20% - 30% 的水平。
阿里面试:每天新增100w订单,如何的分库分表?这份答案让我当场拿了offer
|
缓存 前端开发 API
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
SQL 关系型数据库 数据库连接
"Nacos 2.1.0版本数据库配置写入难题破解攻略:一步步教你排查连接、权限和配置问题,重启服务轻松解决!"
【10月更文挑战第23天】在使用Nacos 2.1.0版本时,可能会遇到无法将配置信息写入数据库的问题。本文将引导你逐步解决这一问题,包括检查数据库连接、用户权限、Nacos配置文件,并提供示例代码和详细步骤。通过这些方法,你可以有效解决配置写入失败的问题。
876 0
|
Web App开发 JavaScript 前端开发
解决DevTools failed to load SourceMap Could not load content for .js.map HTTP error code 404 问题
解决DevTools failed to load SourceMap Could not load content for .js.map HTTP error code 404 问题
1914 0

热门文章

最新文章