2025/12/31 20:50:37
网站建设
项目流程
门户营销型网站搭建,深圳建筑招聘网,门户网站建设不断,沧州做网站哪家公司好01-什么是乐观锁乐观锁 假设并发冲突是小概率事件#xff0c;因此#xff0c;它不会在操作前加锁#xff0c;而是直接去操作数据。在提交更新的时候#xff0c;它会检查一下#xff0c;在自己操作期间#xff0c;数据是否被其他线程修改过如果没有被修改#xff0c;则成…01-什么是乐观锁乐观锁假设并发冲突是小概率事件因此它不会在操作前加锁而是直接去操作数据。在提交更新的时候它会检查一下在自己操作期间数据是否被其他线程修改过如果没有被修改则成功提交更新如果已经被修改则认为发生了冲突更新失败。此时通常会进行重试重新读取数据、执行操作、尝试提交或者直接放弃这种“检查并更新”的机制就是乐观锁的核心02-JUC中乐观锁的实现原理在Java并发工具包(JUC)中乐观锁主要通过CAS(Compare And Swap)机制实现其核心思想是先比较后交换允许多个线程同时访问共享资源但在更新时会检测是否有是否有冲突发生2.1 核心原理 —— CASCAS(compare and swap)中文翻译为比较并交换实现并发算法时常用到的一种技术用于保证共享变量的原子性更新它包含三个操作数 ——内存位置、预期原值与更新值执行CAS操作的时候将内存位置的值与预期原值进行比较如果相匹配那么处理器会自动将该位置更新为新值如果不匹配处理器不做任何操作多个线程同时执行CAS操作只有一个会成功CAS 演示public class CASDemo { public static void main(String[] args) { AtomicInteger atomicInteger new AtomicInteger(5); System.out.println(atomicInteger.compareAndSet(5, 2022) \t atomicInteger.get());//true 2022 System.out.println(atomicInteger.compareAndSet(5, 2022) \t atomicInteger.get());//false 2022 } }硬件级别保证compareAndSet 源码2.2 CAS底层原理 —— Unsafe类Unsafe类是CAS的核心类由于Java方法无法直接访问底层系统需要通过本地native方法来访问Unsafe相当于一个后门基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中其内部方法操作可以像C的指针一样直接操作内存因此Java中CAS操作的执行依赖于Unsafe类的方法。注意Unsafe类中的所有方法都是native修饰的也就是说Unsafe类中的所有方法都直接调用操作系统底层资源执行相应任务问题我们知道i是线程不安全的那AtomicInteger.getAndIncrement()如何保证原子性AtomicInteger类主要利用CAS volatile 和 native方法来保证原子操作从而避免synchronized的高开销执行效率大为提升CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能通过它实现了原子操作。再次强调由于CAS是一种系统原语原语属于操作系统用语范畴是由若干条指令组成的用于完成某个功能的一个过程并且原语的执行必须是连续的在执行过程中不允许被中断也就是说CAS是一条CPU的原子指令不会造成所谓的数据不一致问题03-Java中乐观锁的三种实现方式3.1 使用原子类基于CAS这是最直接、最常用的方式。java.util.concurrent.atomic包提供了一系列原子类如AtomicInteger、AtomicLong、AtomicReference等它们内部就是通过 CAS 来实现无锁的线程安全操作在incrementAndGet()内部线程会不断地读取当前值计算新值然后通过 CAS 尝试更新。如果失败说明有其他线程抢先修改了它会再次循环直到成功。这个过程被称为 “自旋”它避免了线程的阻塞原子类 实现乐观锁import java.util.concurrent.atomic.AtomicInteger; public class OptimisticCounter { // 使用 AtomicInteger 替代普通的 int private AtomicInteger count new AtomicInteger(0); public void increment() { // incrementAndGet() 内部就是一个 CAS 自旋循环 // 不断尝试将当前值 1直到成功为止 count.incrementAndGet(); } public int getCount() { return count.get(); } }3.2 版本号Version机制这是在 数据库 和 业务系统 中非常常用的一种乐观锁实现原理在数据表中增加一个version字段通常是数字类型1. 读取数据当读取数据时将version字段的值一并读出SELECT value, version FROM my_table WHERE id 1;2. 更新数据当提交更新时将之前读到的 version 值作为条件。同时将 version 值加 1UPDATE my_table SET value new_value, version version 1 WHERE id 1 AND version 123;如果更新成功影响行数为 1说明在你操作期间没有其他线程修改过数据如果更新失败影响行数为 0说明version 已经被其他线程修改发生了冲突。此时应用层需要根据业务逻辑决定是重试还是放弃许多 ORM 框架如 JPA/Hibernate都内置了Version注解来自动支持此功能3.3 时间戳TimeStamp机制原理与版本号机制类似只是将version字段换成了一个timestamp字段记录数据的最后更新时间更新逻辑UPDATE my_table SET value new_value, timestamp current_time WHERE id 1 AND timestamp old_time;虽然可行但时间戳机制的精度和可靠性不如版本号机制。例如在分布式系统中可能存在时钟不同步的问题导致判断出错。因此版本号机制通常是更推荐的选择04-CAS的缺点4.1 性能特点分析场景性能表现适用条件低竞争⭐⭐⭐⭐⭐线程数少冲突概率小中等竞争⭐⭐⭐⭐有一定冲突但可接受高竞争⭐⭐大量线程争用频繁重试4.2 循环时间长开销很大getAndAddInt() 方法有一个do while 循环如果CAS失败会一直进行尝试如果CAS长时间一直不成功可能会给CPU带来很大开销4.3 引发ABA问题01-ABA问题怎么产生的CAS算法实现一个重要前提需要提取出内存中某时刻的数据并在当下时刻比较并替换那么在这个时间差内会导致数据的变化比如说一个线程1从内存位置V中取出A这时候另一个线程2也从内存中取出A并且线程2进行了一些操作将值变成了B然后线程2又将V位置的数据变成A这时候线程1进行CAS操作发现内存中仍然是A预期ok然后线程1操作成功 ——尽管线程1的CAS操作成功但是不代表这个过程就是没有问题的02-如何解决带版本号时间戳的原子引用单线程下演示AtomicStampedReference 带版本号解决ABA问题Data AllArgsConstructor NoArgsConstructor class Book { private int id; private String bookName; } public class AtomicStampedReferenceDemo { public static void main(String[] args) { Book javaBook new Book(1, javaBook); AtomicStampedReferenceBook atomicStampedReference new AtomicStampedReference(javaBook, 1); System.out.println(atomicStampedReference.getReference() \t atomicStampedReference.getStamp()); Book mysqlBook new Book(2, mysqlBook); boolean b; // 期望值 更新值 期望版本号 更新版本号 b atomicStampedReference.compareAndSet(javaBook, mysqlBook, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() 1); System.out.println(b \t atomicStampedReference.getReference() \t atomicStampedReference.getStamp()); b atomicStampedReference.compareAndSet(mysqlBook, javaBook, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() 1); System.out.println(b \t atomicStampedReference.getReference() \t atomicStampedReference.getStamp()); } } /** * Book(id1, bookNamejavaBook) 1 * true Book(id2, bookNamemysqlBook) 2 * true Book(id1, bookNamejavaBook) 3 */多线程下演示AtomicStampedReference 带版本号解决ABA问题/** * author Guanghao Wei * create 2023-04-12 15:28 */ public class ABADemo { static AtomicInteger atomicInteger new AtomicInteger(100); static AtomicStampedReferenceInteger atomicStampedReference new AtomicStampedReference(100, 1); public static void main(String[] args) { // abaHappen();//true 2023 abaNoHappen(); } private static void abaNoHappen() { new Thread(() - { int stamp atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() \t 首次版本号: stamp); try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 模拟ABA问题 atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() 1); System.out.println(Thread.currentThread().getName() \t 2次版本号: atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() 1); System.out.println(Thread.currentThread().getName() \t 3次版本号: atomicStampedReference.getStamp()); }, t3).start(); new Thread(() - { int stamp atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() \t 首次版本号: stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } boolean b atomicStampedReference.compareAndSet(100, 200, stamp, stamp 1); System.out.println(b \t atomicStampedReference.getReference() \t atomicStampedReference.getStamp()); }, t4).start(); } private static void abaHappen() { new Thread(() - { atomicInteger.compareAndSet(100, 101); try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } atomicInteger.compareAndSet(101, 100); }, t1).start(); new Thread(() - { try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicInteger.compareAndSet(100, 2023) \t atomicInteger.get());//true 2023 }, t2).start(); } } /** * t3 首次版本号: 1 * t4 首次版本号: 1 * t3 2次版本号: 2 * t3 3次版本号: 3 * false 100 3 */一句话比较时加版本号一起上05-应用场景总结明确一点JUC中的乐观锁适用于读多写少、冲突较少的场景5.1 原子类Atomic Classes场景计数器、状态标志、累加操作等AtomicInteger count new AtomicInteger(0); count.incrementAndGet(); // CAS实现自增适用于高并发计数器适用场景网站访问量统计并发累加计算5.2乐观锁版本控制场景数据库乐观锁、数据版本控制AtomicStampedReferenceInteger versionedRef new AtomicStampedReference(100, 0); // 初始值100版本0 int[] stampHolder new int[1]; int current versionedRef.get(stampHolder); versionedRef.compareAndSet(current, 200, stampHolder[0], stampHolder[0] 1);适用场景数据库更新防止覆盖如库存扣减版本号控制的数据同步5.3 非阻塞数据结构场景无锁队列、栈、哈希表ConcurrentLinkedQueueString queue new ConcurrentLinkedQueue(); // 内部基于CAS queue.offer(data);适用场景高并发消息队列无锁线程池任务管理5.4 状态机转换场景状态机的无锁状态切换AtomicReferenceState state new AtomicReference(State.IDLE); state.compareAndSet(State.IDLE, State.RUNNING); // 仅当空闲时才转为运行适用场景连接池状态管理任务执行状态控制5.5单例模式的双重检查锁优化场景使用AtomicReference实现无锁单例private static final AtomicReferenceSingleton INSTANCE new AtomicReference(); public static Singleton getInstance() { Singleton instance INSTANCE.get(); if (instance null) { instance new Singleton(); if (INSTANCE.compareAndSet(null, instance)) { return instance; } } return INSTANCE.get(); }6.累加器与收集器场景并行流中的无锁统计LongAdder adder new LongAdder(); // 内部使用分段CAS优化高并发 parallelStream.forEach(e - adder.increment());适用场景并行计算统计结果高性能计数器比AtomicLong更优5.7自旋锁实现场景短时间等待的锁操作AtomicBoolean lock new AtomicBoolean(false); while (!lock.compareAndSet(false, true)) { // 自旋等待 } try { /* 临界区 */ } finally { lock.set(false); }适用场景临界区执行时间极短的场景5.8 注意事项ABA问题使用AtomicStampedReference或AtomicMarkableReference解决竞争激烈时性能下降大量线程CAS失败会导致频繁重试此时考虑锁升级或LongAdder仅保证单一变量原子性复合操作需结合compareAndSet循环或锁场景推荐实现理由简单计数器AtomicInteger轻量、无锁高并发统计LongAdder分段CAS减少竞争对象引用更新AtomicReference无锁对象引用管理需版本控制的数据AtomicStampedReference解决ABA问题无锁队列ConcurrentLinkedQueue非阻塞高性能总之乐观锁在低冲突、高并发读的场景下性能显著优于悲观锁但需根据实际竞争情况谨慎选择