StampedLock悲观锁乐观锁

本文深入探讨了Java 1.8引入的StampedLock,一种性能优于ReadWriteLock的锁机制。StampedLock支持写锁、悲观读和乐观读三种模式,允许多个线程同时获取乐观锁和悲观读锁,但仅允许一个线程获取写锁。文章还提供了StampedLock在代码中的应用实例,展示了如何在读取数据时结合乐观读和悲观读以提高效率。

StampedLock java1.8提供的, 性能比ReadWriteLock好.

语法
	final StampedLock sl = new StampedLock();
 
	/**
 	* 悲观读锁
 	* 与ReadWriteLock的读锁相似
 	* 允许多个线程进行读操作
 	* 与写锁互斥
	**/
	//获取悲观锁
	long stamp = sl.readLock();
	try {
      //。。。。
    }finally {
   	  sl.unlockRead(stamp);
   	}


	/**
 	* 写锁
 	* 与ReadWriteLock的写锁相似
 	* 只允许一个线程进行写操作
 	* 与读锁互斥
	**/
     //获取写锁
	long stamp1 = sl.writeLock();
	try{
        //。。。
    }finally {
        //释放写锁
        sl.unlockWrite(stamp1);
    }


	/**
 	* 乐观读锁升级为悲观读
 	* 锁的严苛程度变强叫做升级,反之叫做降级
	**/
	//获取乐观锁
 	long stamp = sl.tryOptimisticRead();
	 //判断执行读操作期间,是否存在写操作,如果存在则sl.validate返回false
    if (!sl.validate(stamp)){
        //升级为悲观锁
        stamp = sl.readLock();
        try{
            //。。。
        }finally {
            //释放悲观读锁
            sl.unlockRead(stamp);
        }

StampedLock提供的乐观读,是允许一个线程获取写锁的,也就是说不是所有的写操作都是被阻塞的。

乐观读和乐观锁是不一样的,乐观读这个操作是无锁的,乐观读认为读的时候不会有写的操作。

示例
import java.util.concurrent.locks.StampedLock;

/**
 * 悲观锁乐观锁
 **/
public class Point {
    private int x;
    private int y;
    final StampedLock sl = new StampedLock();

    //计算到原点的距离
    int distanceFromOrigin() throws  Exception{
        //乐观锁
        long stamp = sl.tryOptimisticRead();
        //读入局部变量,读的过程数据可能被修改
        int curX = x;
        int curY = y;

        //判断执行读操作期间,是否存在写操作,如果存在则sl.validate返回false
        if (!sl.validate(stamp)){
            //升级为悲观锁
            stamp = sl.readLock();
            try{
                curX = x;
                curY = y;
            }finally {
                //释放悲观读锁
                sl.unlockRead(stamp);
            }
        }

        return (int)Math.sqrt(curX*curX   curY*curY);
    }
}
特征
  1. StampedLock支持三种模式: 写锁、悲观读和乐观读。
  2. 允许多 个线程同时获取乐观锁和悲观读锁。
  3. 只允许一个线程获取写锁,写锁和悲观读锁是互斥的。
  4. 使用StampedLock一定不要调用中断操作,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁writeLockInterruptibly()。
  5. StampedLock里的写锁和悲观读锁加锁成功之后,都会返回一个stamp;然后解锁的时候,需要传入这个 stamp。
  6. StampedLock不支持重入(ReadWriteLock支持)。
  7. StampedLock的悲观读锁、写锁都不支持条件变量。
  8. StampedLock支持锁的降级(通过tryConvertToReadLock()方法实现)和升级(通过 tryConvertToWriteLock()方法实现),但是建议你要慎重使用。
数据库中的乐观锁
  1. 在表中添加一个数值型版本号字段version。
  2. 每次更新表时都将version字段加1。
  3. 修改数据时使用主键和version作为修改条件,version一致则会修改成功。
  4. 如果修改失败,通过主键查询获取最新数据再执行修改。
[JAVA工程师必会知识点之并发编程]1、现在几乎100%的公司面试都必须面试并发编程,尤其是互联网公司,对于并发编程的要求更高,并发编程能力已经成为职场敲门砖。2、现在已经是移动互联大数据时代,对于应用程序的性能、处理能力、处理时效性要求更高了,传统的串行化编程无法充分利用现有的服务器性能。3、并发编程是几乎所有框架的底层基础,掌握好并发编程更有利于我们学习各种框架。想要让自己的程序执行、接口响应、批处理效率更高,必须使用并发编程。4、并发编程是中高级程序员的标配,是拿高薪的必备条件。 【主讲讲师】尹洪亮Kevin:现任职某互联网公司首席架构师,负责系统架构、项目群管理、产品研发工作。10余年软件行业经验,具有数百个线上项目实战经验。擅长JAVA技术栈、高并发高可用伸缩式微服务架构、DevOps。主导研发的蜂巢微服务架构已经成功支撑数百个微服务稳定运行【推荐你学习这门课的理由:知识体系完整+丰富学习资料】1、 本课程总计122课时,由五大体系组成,目的是让你一次性搞定并发编程。分别是并发编程基础、进阶、精通篇、Disruptor高并发框架、RateLimiter高并发访问限流吗,BAT员工也在学。2、课程附带附带3个项目源码,几百个课程示例,5个高清PDF课件。3、本课程0基础入门,从进程、线程、JVM开始讲起,每一个章节只专注于一个知识点,每个章节均有代码实例。 【课程分为基础篇、进阶篇、高级篇】一、基础篇基础篇从进程与线程、内存、CPU时间片轮训讲起,包含线程的3种创建方法、可视化观察线程、join、sleep、yield、interrupt,Synchronized、重入锁、对象锁、类锁、wait、notify、线程上下文切换、守护线程、阻塞式安全队列等内容。二、进阶篇进阶篇课程涵盖volatied关键字、Actomic类、可见性、原子性、ThreadLocal、Unsafe底层、同步类容器、并发类容器、5种并发队列、COW容器、InheritableThreadLocal源码解析等内容。三、精通篇精通篇课程涵盖JUC下的核心工具类,CountDownLath、CyclicBarrier、Phaser、Semaphore、Exchanger、ReentrantLock、ReentrantReadWriteLock、StampedLock、LockSupport、AQS底层、悲观锁乐观锁、自旋锁、公平锁、非公平锁、排它锁、共享锁、重入锁、线程池、CachedThreadPool、FixedThreadPool、ScheduledThreadPool、SingleThreadExecutor、自定义线程池、ThreadFactory、线程池切面编程、线程池动态管理等内容,高并发设计模式,Future模式、Master Worker模式、CompletionService、ForkJoin等课程中还包含Disruptor高并发无锁框架讲解:Disruptor支持每秒600万订单处理的恐怖能力。深入到底层原理开发模式,让你又懂又会用。高并发访问限流讲解:涵盖木桶算法、令牌桶算法、Google RateLimiter限流开发、Apache JMeter压力测试实战。 【学完后我将达到什么水平?】1、 吊打一切并发编程相关的笔试题、面试题。2、 重构自己并发编程的体系知识,不再谈并发色变。3、 精准掌握JAVA各种并发工具类、方法、关键字的原理使用。4、 轻松上手写出更高效、更优雅的并发程序,在工作中能够提出更多的解决方案。  【面向人群】1、 总感觉并发编程很难、很复杂、不敢学习的人群。2、 准备跳槽、找工作、拿高薪的程序员。3、 希望提高自己的编程能力,开发出更高效、性能更强劲系统的人群。4、 想要快速、系统化、精准掌握并发编程的人群。【课程知识体系图】
<think>我们正在讨论乐观锁悲观锁的概念及区别,特别是在Java中的实现原理。根据引用内容,我们可以整理如下: 1. **悲观锁**: - 概念:总是假设最坏情况,每次访问共享资源时都会上锁,确保同一时间只有一个线程能访问该资源。 - 实现:在Java中,`synchronized`关键字`ReentrantLock`是典型的悲观锁实现。 - 适用场景:写多读少,竞争激烈的场景,可以避免频繁的失败重试。 - 缺点:加锁会带来额外的性能开销,并且可能导致线程阻塞。 2. **乐观锁**: - 概念:假设冲突很少发生,因此在访问共享资源时不上锁,而是在更新数据时检查在此期间是否有其他线程修改了数据(通常通过版本号或CAS操作)。 - 实现:在Java中,原子类(如`AtomicInteger`)使用CAS(Compare and Swap)操作实现乐观锁;数据库中使用版本号字段。 - 适用场景:读多写少,竞争较少的场景,可以避免加锁的开销。 - 缺点:如果冲突频繁,重试操作可能反而降低性能。 **区别对比**: | 特性 | 悲观锁 | 乐观锁 | |--------------|---------------------------|---------------------------| | 加锁时机 | 操作前先加锁 | 操作时不上锁,提交时检查 | | 实现方式 | `synchronized`, `ReentrantLock` | CAS, 版本号 | | 适用场景 | 写操作多,竞争激烈 | 读操作多,竞争较少 | | 性能开销 | 固定,可能阻塞线程 | 无冲突时开销小,冲突时需重试 | | 数据一致性 | 强一致性 | 最终一致性 | **实现原理**: 1. **悲观锁的实现原理**: - 在Java中,`synchronized`关键字通过监视器锁(Monitor)实现,每个对象都有一个监视器锁,当线程进入同步代码块时,会自动获取锁,退出时释放锁。在JVM内部,它通过进入退出管程(Monitor)来实现,底层使用操作系统的互斥量(Mutex)来实现线程阻塞。 - `ReentrantLock`则是基于AQS(AbstractQueuedSynchronizer)框架实现,通过CAS操作队列来管理线程的阻塞唤醒。 2. **乐观锁的实现原理**: - CAS(Compare and Swap)操作:CAS是一种无锁算法,包含三个操作数——内存位置(V)、预期原值(A)新值(B)。当且仅当V的值等于A时,CAS才会将V的值更新为B,否则什么都不做。整个操作是原子性的,由硬件(如CPU的cmpxchg指令)保证。 - 版本号机制:在数据库中,可以为数据增加一个版本号字段。每次更新时,先读取当前版本号,更新时检查版本号是否改变,如果改变则重试。 **Java代码示例**: - 悲观锁示例(使用`synchronized`): ```java public class Counter { private int count; public synchronized void increment() { count++; } } ``` - 乐观锁示例(使用`AtomicInteger`): ```java import java.util.concurrent.atomic.AtomicInteger; public class Counter { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } } ``` **适用场景选择**: - 根据引用[5]:读多写少用乐观锁(配合Atomic类或StampedLock),写多读少用悲观锁(配合ReentrantLock或synchronized)。 - 高并发场景:考虑分段锁(如ConcurrentHashMap)或LongAdder(减少CAS空转)。 **总结**: 乐观锁悲观锁是两种不同的并发控制策略,选择哪种取决于具体的应用场景。乐观锁在无冲突时性能更高,而悲观锁在冲突频繁时更稳定。 §§相关问题§§ 1. 什么是CAS操作?它存在哪些问题? 2. 在分布式系统中如何实现乐观锁悲观锁? 3. Java中除了synchronizedReentrantLock,还有哪些锁机制? 4. 乐观锁在哪些情况下会失效?如何解决? 5. 如何根据系统负载选择合适的锁策略?</think>### Java中乐观锁悲观锁的区别及实现原理 #### 一、核心概念对比 | 特性 | 悲观锁 | 乐观锁 | |--------------|-----------------------------------|-----------------------------------| | **基本思想** | 假设冲突必然发生,先加锁再操作数据 | 假设冲突很少发生,操作后检查冲突 | | **加锁时机** | 操作前显式加锁 | 操作时不加锁,提交时验证版本 | | **性能影响** | 高(线程阻塞) | 低(无阻塞,冲突时重试) | | **适用场景** | 写多读少,竞争激烈 | 读多写少,冲突率低 | | **实现方式** | `synchronized`, `ReentrantLock` | CAS, 版本号机制 | #### 二、实现原理详解 1. **悲观锁实现原理** - 基于**阻塞机制**:线程访问共享资源前必须先获得锁 - Java实现: ```java // synchronized实现 public synchronized void updateData() { // 临界区操作 } // ReentrantLock实现 private Lock lock = new ReentrantLock(); public void updateData() { lock.lock(); // 显式加锁 try { // 临界区操作 } finally { lock.unlock(); // 必须释放锁 } } ``` - 底层机制:通过操作系统的互斥量(Mutex)实现线程阻塞,JVM使用**监视器锁(Monitor)** 管理`synchronized`[^3] 2. **乐观锁实现原理** - 基于**版本控制**或**CAS(Compare and Swap)** - Java实现: ```java // CAS实现(AtomicInteger示例) private AtomicInteger value = new AtomicInteger(0); public void safeIncrement() { int oldValue; do { oldValue = value.get(); // 获取当前值 } while (!value.compareAndSet(oldValue, oldValue + 1)); // CAS更新 } // 版本号实现(数据库场景) UPDATE table SET data=new_data, version=version+1 WHERE id=record_id AND version=current_version; ``` - CAS底层:依赖CPU的**原子指令**(如x86的`cmpxchg`),无需线程阻塞[^2] #### 三、关键区别分析 1. **并发控制方式** - 悲观锁:**预防冲突**(先加锁后操作) - 乐观锁:**检测冲突**(先操作后验证) 2. **数据一致性** - 悲观锁:强一致性(串行操作) - 乐观锁:最终一致性(可能需重试) 3. **性能对比** ```mermaid graph LR A[线程竞争程度] -->|低| B[乐观锁更高效] A -->|高| C[悲观锁更稳定] ``` 当冲突率<20%时,乐观锁性能优势明显;冲突率>40%时悲观锁更优[^5] #### 四、典型应用场景 1. **悲观锁适用场景** - 银行转账操作(高竞争写) - 库存扣减(防止超卖) - 数据库事务中的`SELECT FOR UPDATE` 2. **乐观锁适用场景** - 商品浏览计数(读多写少) - 缓存更新(低冲突率) - 分布式系统状态同步 #### 五、选型建议 1. 优先考虑乐观锁: - 读操作远多于写操作 - 冲突概率低于20% - 需要高吞吐量的场景 2. 选择悲观锁: - 写操作频繁且竞争激烈 - 业务逻辑复杂需要强一致性 - 无法容忍重试带来的延迟[^5] > **实践提示**:在Java中,`LongAdder`通过分段CAS机制解决了高并发下乐观锁的频繁重试问题,是`AtomicLong`的优化替代方案[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘彦青-Yannis

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值