悲观锁

简介: 悲观锁是一种并发控制机制,假设数据在访问时易被修改,故在操作前加锁以确保线程安全。其优点为强一致性与实现简单,但性能开销大、易阻塞,适用于写多、一致性要求高的场景,如金融交易。常见实现包括数据库行锁、表锁及Java中的`synchronized`与`ReentrantLock`。

悲观锁(Pessimistic Locking)是一种传统的并发控制机制,假设数据在访问过程中极有可能被其他线程修改,因此在数据访问前先加锁,确保同一时间只有一个线程能操作数据。这种策略通过牺牲并发性能来保证数据的一致性。

核心原理

  1. 独占资源
    线程在访问数据前先获取锁,其他线程必须等待锁释放才能访问。常见实现包括:

    • 数据库层面:行锁、表锁。
    • 编程语言层面synchronized关键字、ReentrantLock
  2. 锁的粒度

    • 细粒度锁:如行锁,并发度高但实现复杂。
    • 粗粒度锁:如表锁,实现简单但可能导致大量线程阻塞。

悲观锁的优缺点

  • 优点

    • 强一致性:确保数据在任何时刻只能被一个线程修改。
    • 简单易用:多数编程语言和数据库提供内置支持。
  • 缺点

    • 性能开销大:线程阻塞导致上下文切换频繁。
    • 可能导致死锁:多个线程循环等待对方释放锁。
    • 不适用于读多写少场景:读操作也会被阻塞,浪费资源。

实现方式

1. 数据库悲观锁

  • 行锁

    SELECT * FROM table WHERE id = ? FOR UPDATE; -- MySQL
    
    • 事务提交前,其他线程无法修改该行数据。
  • 表锁

    LOCK TABLES table WRITE; -- MySQL
    
    • 锁表期间,其他线程无法读写该表。

2. Java语言实现

  • synchronized关键字

    public class SynchronizedExample {
         
        private final Object lock = new Object();
    
        public void writeData() {
         
            synchronized (lock) {
         
                // 临界区代码,同一时间仅一个线程执行
            }
        }
    }
    
  • ReentrantLock

    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockExample {
         
        private final ReentrantLock lock = new ReentrantLock();
    
        public void writeData() {
         
            lock.lock();
            try {
         
                // 临界区代码
            } finally {
         
                lock.unlock(); // 必须在finally中释放锁
            }
        }
    }
    

悲观锁的典型应用场景

  1. 写操作频繁:如库存扣减、账户转账。
  2. 数据一致性要求高:如金融系统交易处理。
  3. 长事务操作:如跨多个资源的复杂业务流程。

与乐观锁的对比

特性 悲观锁 乐观锁
加锁时机 访问数据前 更新数据时
实现方式 synchronized、数据库锁 CAS、版本号机制
适用场景 写多冲突多、长事务 读多写少、冲突少
线程状态 阻塞等待锁释放 无阻塞,冲突时重试
一致性保证 强一致性 弱一致性(冲突时可能失败)

注意事项

  1. 死锁预防

    • 按相同顺序获取锁,设置锁超时时间。
    • 使用ReentrantLock.tryLock()避免死锁。
  2. 锁粒度优化

    • 避免在大方法上加锁,减小锁的范围。
  3. 性能监控

    • 高并发场景下,通过工具(如JStack)监控锁竞争情况。

Java中的悲观锁工具

  1. synchronized关键字

    • 隐式锁,由JVM自动释放。
  2. ReentrantLock

    • 显式锁,支持公平锁、可中断锁、条件变量。
  3. ReadWriteLock

    • 读写分离锁,允许多个线程同时读,但写时独占。
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class ReadWriteLockExample {
         
        private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    
        public void readData() {
         
            rwLock.readLock().lock();
            try {
         
                // 允许多个线程同时读
            } finally {
         
                rwLock.readLock().unlock();
            }
        }
    
        public void writeData() {
         
            rwLock.writeLock().lock();
            try {
         
                // 同一时间仅一个线程写
            } finally {
         
                rwLock.writeLock().unlock();
            }
        }
    }
    

总结

悲观锁通过提前加锁确保数据一致性,适用于写操作频繁、冲突严重的场景。但在高并发读场景下,过度使用会导致性能瓶颈。现代系统通常结合悲观锁与乐观锁,根据业务特点选择合适的并发控制策略,以平衡性能与数据安全。

目录
相关文章
|
4月前
|
存储 安全 Java
synchronized 锁升级
JDK 6 引入的 synchronized 锁升级机制,通过偏向锁、轻量级锁和重量级锁的动态切换,优化了多线程同步性能。该机制根据竞争情况逐步升级锁状态,减少线程阻塞和系统调用开销,从而提升并发效率。
175 0
|
4月前
|
XML 人工智能 数据格式
如何写好提示词Prompt?
本文由产品专家三桥君撰写,主要探讨如何写出高质量的Prompt,助力AI模型输出优质内容。文章从三个核心方面展开:理解大语言模型(LLM)、积累行业Know-how、提升逻辑表达清晰性。作者结合自身实践经验,强调在AI技术快速发展的背景下,提升Prompt能力的关键在于夯实基础,深入行业,精准表达。通过本文,读者将获得实用的Prompt优化思路,提升AI应用效率。
190 0
|
4月前
|
Java Maven 开发工具
SpringBoot使用汇总
本节介绍 Spring Boot 工程的构建方法,包括使用 IDEA 快速创建项目、通过官方平台生成项目、配置 Maven 以及设置编码格式等内容。涵盖 Group 和 Artifact 的填写规范、依赖添加、IDE 配置与推荐设置,助力快速搭建开发环境。
160 3
|
4月前
|
Java 应用服务中间件 Maven
SpringBoot使用汇总
本节介绍了Spring Boot项目工程结构,包含src/main/java(业务代码)、src/main/resources(静态与配置文件)和src/test/java(测试代码)。通过@SpringBootApplication注解的启动类运行main方法即可快速启动应用。Spring Boot内置Tomcat,简化配置流程。示例展示了创建Controller、访问接口及修改默认端口的方法,帮助开发者快速上手Spring Boot开发。
125 2
|
4月前
HTTP协议中常见的状态码 ?
HTTP协议状态码分为1xx、2xx、3xx、4xx、5xx五类。常见状态码包括:101(切换协议)、200(请求成功)、302(重定向)、401(未认证)、404(资源未找到)、500(服务器错误)。
343 0
|
4月前
|
搜索推荐 算法 大数据
快速排序的实现思路
快速排序(Quicksort)由托尼·霍尔于1960年提出,是一种高效的分治排序算法。其核心思想是通过选取基准元素将数组划分为两部分,递归地对左右子数组排序。算法平均时间复杂度为 $O(n \log n)$,具有原地排序、空间利用率高等优点,广泛应用于大数据排序场景。合理选择基准和优化策略可显著提升性能,是实际应用中最常用的排序算法之一。
105 0
|
4月前
|
搜索推荐 Python
为啥说选择排序是不稳定的
选择排序是一种简单但不稳定的排序算法。它通过每轮选择最小元素并交换位置来实现排序,但这种交换可能破坏相同值元素的相对顺序。例如对数组 `[5, 8, 5, 2]` 排序后,两个 `5` 的顺序会发生变化,从而证明其不稳定性。
277 0
|
存储 弹性计算 固态存储
阿里云服务器收费标准租用价格及价格计算器使用参考
阿里云服务器租用价格参考,不同时期阿里云服务器的租用价格不同,2024年阿里云多款云服务器的收费标准都做了降价调整,最高降幅达93%,同时,阿里云还推出了多款价格比较实惠的云服务器,现在购买阿里云轻量应用服务器2核2G3M带宽82元1年,经济型e实例ECS云服务器2核2G3M带宽新购和续费优惠价99元1年,通用算力型u1实例2核4G5M带宽新购和续费优惠价199元1年,4核8G云服务器955元1年,本文为大家介绍一下阿里云服务器的最新收费标准租用价格以及使用价格计算器查询云服务器价格的方法。
|
存储 缓存 NoSQL
Redis多级缓存指南:从前端到后端全方位优化!
本文探讨了现代互联网应用中,多级缓存的重要性,特别是Redis在缓存中间件的角色。多级缓存能提升数据访问速度、系统稳定性和可扩展性,减少数据库压力,并允许灵活的缓存策略。浏览器本地内存缓存和磁盘缓存分别优化了短期数据和静态资源的存储,而服务端本地内存缓存和网络内存缓存(如Redis)则提供了高速访问和分布式系统的解决方案。服务器本地磁盘缓存因I/O性能瓶颈和复杂管理而不推荐用于缓存,强调了内存和网络缓存的优越性。
1293 47
|
Oracle Java 关系型数据库
@Id、@GeneratedValue的作用,以及@GeneratedValue的使用
@Id、@GeneratedValue的作用,以及@GeneratedValue的使用