JAVA进阶-锁
1.悲观锁和乐观锁悲观锁在修改数据时一定有别的线程来使用一定会发生并发冲突所以在获取数据的时候会加锁。JAVA中的synchronized和lock都是悲观锁。乐观锁在修改数据时一定没有别的线程来使用所以不会添加锁。但是在更新数据的时候会查看有没有线程修改数据。比如版本号和CAS原理无锁原理实现方式悲观锁数据库层面select * from dual for update;JAVA层面synchronized、ReentrantLock 等独占锁。乐观锁数据库层面UPDATE table SET ...version version 1 where id ? and version ?还有时间戳、CAS。优点悲观锁强一致性写多场景非常安全不会出现并发覆盖问题乐观锁无锁性能极高高并发友好不会阻塞缺点悲观锁性能差容易阻塞、死锁高并发下吞吐量极低乐观锁冲突多时重试成本高业务代码要处理重试流程悲观锁上锁读取 / 修改释放锁乐观锁读取数据拿到 version业务处理更新时带上 version 做条件影响行数 0 → 冲突重试 / 报错使用场景悲观锁写多读少金融、库存扣减、订单支付等强一致场景乐观锁读多写少商品详情、点赞、统计、日志等1.1 CAS全面解析乐观锁的核心实现CAS是 Compare And Swap(比较并交换)的缩写是实现乐观锁的核心技术也是CPU层面提供的原子指令非JAVA语法可以理解为我认为内存里的值是A现在要改成B先检查内存里是不是真的是A如果是就改成B不是就不改。1.1.1 CAS核心思想3个核心值CAS 操作依赖3个关键参数1.内存地址V要操作的变量在内存中的位置。2.预期值A认为当前内存中应该有的值。3.新值B想要更新成的新值。执行逻辑一步到位的原子操作if (内存值 V 预期值 A) { 将内存值 V 改为 新值 B 返回 成功true } else { 不做任何修改 返回 失败false }关键整个“比较 交换” 是原子性的CPU保证不会被线程打断所以不需要加锁也能保证并发安全。1.1.2 CAS 通俗举例比如你和朋友同时给一个共享的计数器 count 加1 初始值 01. 读取到 count 0 预期值 A 0准备改成 1 新值 B 12.CAS检查内存中 count 是不是 0如果是 直接改成1返回成功如果已经先把 count 改成 1 检查失败返回失败 (需要重新读取最新值再重试)1.1.3 JAVA中的CAS实战JAVA 本身不能直接调用CPU指令但JDK 提供了java.util.concurrent.atomic 包封装了CAS操作最常用的是AtomicIntegerimport java.util.concurrent.atomic.AtomicInteger; public class CASDemo { public static void main(String[] args) { // 初始化值为 0 AtomicInteger count new AtomicInteger(0); // 核心 CAS 操作compareAndSet(预期值, 新值) boolean result1 count.compareAndSet(0, 1); System.out.println(第一次更新 result1); // true更新成功count1 // 再次用 0 作为预期值更新此时内存值是 1 boolean result2 count.compareAndSet(0, 2); System.out.println(第二次更新 result2); // false更新失败 // 用最新值 1 作为预期值更新 boolean result3 count.compareAndSet(1, 2); System.out.println(第三次更新 result3); // true更新成功count2 } }常用简化方法 底层还是CASAtomicInteger提供了更易用的方法 如incrementAndGet()底层都是 CAS 自旋重试// 等价于 count但线程安全CAS 自旋重试直到成功 int newCount count.incrementAndGet(); System.out.println(newCount); // 31.1.4 CAS的优点 缺点优点1.无锁不需要加 synchronized 或 Lock 避免线程阻塞和上下文切换性能极高。2.原子性 CPU 级别的原子操作比锁更轻量。3.并发友好高并发下吞吐量远高于悲观锁。缺点1.ABA问题最核心问题变量从A - B - A,CAS 检查时发现是A会误以为没被修改但实际已经被改过。解决JDK 提供 AtomicStampedReference 加版本号/时间戳类似乐观锁的版本号机制2.自旋开销如果并发冲突频繁CAS会一直重试自旋消耗 CPU 资源3.只能操作单个变量CAS只能保证单个变量的原子性无法解决多个变量的原子操作 需用锁或AtomicInteger 封装对象2.自旋锁和自适应自旋锁2.1 自旋锁基于CAS实现的一种非阻塞锁,核心思想是线程获取锁失败时不直接阻塞而是再循环中不断重试自旋直到成功获取锁。而自适应自旋锁是自旋锁的优化版本会根据“过往经验”动态调整自旋次数是JVM中 Synchronized 锁升级的重要环节。2.1.1 为什么需要自旋锁传统悲观锁如 synchronized 早期实现获取锁失败时线程会被挂起从运行态到阻塞态这涉及操作系统的上下文切换开销很大。但实际场景中很多锁的持有时间极短比如几纳秒线程挂起再唤醒的时间可能比锁占用的时间还长。此时自旋重试比阻塞更高效——相当于“在门口等几秒而不是直接回家阻塞”2.1.2 自旋锁核心原理1.基于CAS原子操作实现线程自旋时不断用 CAS 检查锁是否被释放。2.自旋过程中线程处于运行态不是阻塞态,只是空耗CPU直到获取锁或自旋结束。2.1.3 JAVA 手动实现自旋锁import java.util.concurrent.atomic.AtomicBoolean; // 自旋锁核心CAS 循环重试 public class SpinLock { // 用 AtomicBoolean 标记锁状态false未锁定true已锁定 private AtomicBoolean locked new AtomicBoolean(false); // 获取锁自旋直到 CAS 成功 public void lock() { // CAS 尝试将 locked 从 false 改为 true while (!locked.compareAndSet(false, true)) { // 自旋空循环直到成功 // 注意实际开发中可加 Thread.yield() 让出CPU减少空耗 } } // 释放锁直接置为 false public void unlock() { locked.set(false); } // 测试 public static void main(String[] args) { SpinLock spinLock new SpinLock(); // 线程1获取锁 new Thread(() - { spinLock.lock(); try { System.out.println(线程1获取锁执行任务); Thread.sleep(100); // 模拟短时间持有锁 } catch (InterruptedException e) { e.printStackTrace(); } finally { spinLock.unlock(); System.out.println(线程1释放锁); } }).start(); // 线程2自旋等待锁 new Thread(() - { spinLock.lock(); try { System.out.println(线程2获取锁执行任务); } finally { spinLock.unlock(); System.out.println(线程2释放锁); } }).start(); } }执行结果线程1获取锁执行任务 线程1释放锁 线程2获取锁执行任务 线程2释放锁线程 2 在获取锁失败时会在while循环中自旋直到线程 1 释放锁后 CAS 成功。2.1.4 自旋锁的优缺点优点缺点无上下文切换开销短持有时间锁性能极高自旋空耗 CPU高冲突时导致 CPU 使用率飙升非阻塞线程不会被挂起自旋次数固定无法适配不同场景比如锁持有时间长时自旋纯浪费实现简单基于 CAS 即可可能导致 活锁多个线程同时自旋重试互相抢不到锁2.2 自适应自旋锁自适应自旋锁是 JDK 1.6 为synchronized引入的优化解决了普通自旋锁 自旋次数固定 的问题。2.2.1 核心原理自旋次数不固定而是根据「上一次获取该锁的自旋情况」动态调整如果上一次自旋成功获取了锁说明该锁的持有时间短本次自旋次数会增加比如从 10 次→20 次认为这次也大概率能快速拿到锁。如果上一次自旋失败了说明该锁的持有时间长本次自旋次数会减少甚至直接不自旋直接进入阻塞避免浪费 CPU。如果自旋次数超过阈值自动切换为阻塞传统悲观锁方式。2.2.2 自适应自旋锁的优势智能适配场景不用手动设置自旋次数JVM 自动根据历史经验调整。平衡性能与资源短锁自旋省开销长锁不自旋省 CPU。是 synchronized 锁升级的关键synchronized的锁升级流程无锁 → 偏向锁 → 轻量级锁自适应自旋 → 重量级锁。2.3 自旋锁 VS 自适应自旋锁 对比特性普通自旋锁自适应自旋锁自旋次数固定比如 10 次 / 20 次动态调整基于历史经验灵活性低高CPU 利用率高冲突时易空耗更合理减少无效自旋适用场景锁持有时间固定的简单场景复杂并发场景如 JVM 内部典型应用手动实现的简单自旋锁JDK 1.6 的 synchronized2.4 实战注意事项自旋锁适合短锁只有锁持有时间极短微秒 / 纳秒级时自旋才有意义如果锁持有时间长自旋会浪费大量 CPU。避免自旋次数过多手动实现自旋锁时可设置最大自旋次数比如 1000 次超过则放弃自旋转为阻塞。JVM 对自旋的控制可通过 JVM 参数调整自旋行为-XX:PreBlockSpin设置普通自旋锁的最大自旋次数JDK 1.6 后该参数失效自适应自旋接管。-XX:-UseSpinning关闭自旋锁所有失败直接阻塞。3.无锁、偏向锁、轻量级锁、重量级锁这些锁状态是 Java 为了优化synchronized关键字性能而设计的锁升级机制核心思想是在不同并发程度下使用成本最低的锁策略从无锁到重量级锁是一个单向升级的过程不会降级。3.1 无锁定义没有任何线程竞争资源所有线程都可以自由访问共享资源。本质不是真正的“锁”而是通过 CAS 乐观锁机制保证操作原子性避免阻塞。适合场景几乎无并发竞争的场景单线程操作。import java.util.concurrent.atomic.AtomicInteger; public class NoLockDemo { // 原子类底层通过 CAS 实现无锁操作 private static AtomicInteger count new AtomicInteger(0); public static void main(String[] args) { // 多线程自增但底层用 CAS 无锁实现 for (int i 0; i 5; i) { new Thread(() - { count.incrementAndGet(); // CAS 自增无锁竞争 System.out.println(Thread.currentThread().getName() : count.get()); }).start(); } } }3.2 偏向锁定义当只有一个线程反复获取同一把锁时JVM 会让该线程“偏向”这把锁——锁对象会记录当前线程的ID后续该线程获取锁时无需任何竞争操作直接返回。核心优化消除无竞争场景下的锁获取/释放开销比轻量级锁更高效。适用场景单一线程重复获取锁的场景单线程循环加锁。触发升级当有第二个线程尝试获取锁时偏向锁会升级为轻量级锁。关键特点默认开启JDK 1.6,可通过 -XX-UseBiasedLocking 关闭。偏向锁的撤销有一定延迟约4秒避免频繁撤销影响性能。3.3 轻量级锁定义当多个线程交替获取同一把锁无激烈竞争时JVM会使用轻量级锁——线程通过CAS操作尝试获取锁失败时不会立即阻塞而是自旋循环重试。核心优化避免线程阻塞/唤醒的内核态切换开销 自旋是用户态操作。适用场景多线程交替竞争锁短时间、低冲突。触发升级当自旋次数达到阈值默认 10 次或自旋线程数超过 CPU 核心数的一半时轻量级锁升级为重量级锁。3.4 重量级锁定义依赖操作系统的互斥量Mutex实现线程获取锁失败时会被挂起进入阻塞状态直到锁释放后被唤醒。核心特点开销大涉及用户态—— 内核态切换线程阻塞/唤醒成本高。适用场景高并发、长持有锁的场景自旋已无意义。synchronized 触发重量级锁public class HeavyLockDemo { private static final Object lock new Object(); public static void main(String[] args) { // 两个线程同时竞争锁最终触发重量级锁 new Thread(() - { synchronized (lock) { try { Thread.sleep(1000); // 长时间持有锁触发锁升级 System.out.println(线程1持有锁); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(() - { synchronized (lock) { System.out.println(线程2获取锁); } }).start(); } }锁状态升级流程单向3.5 总结无锁基于 CAS 实现无竞争时最优无锁开销偏向锁单线程专属消除无竞争下的锁操作开销有新线程竞争时升级轻量级锁多线程交替竞争自旋 CAS 获取锁自旋失败则升级为重量级锁重量级锁依赖操作系统互斥量线程阻塞 / 唤醒开销大适用于高并发长持有场景。核心逻辑JVM 从 “乐观” 到 “悲观” 逐步调整锁策略尽可能降低锁的使用成本。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2425354.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!