【JUC】关于锁的那点事
Java中的锁分类乐观锁悲观锁乐观锁持乐观态度认为并发冲突的概率较低在提交时检查数据是否被修改若未被修改则提交否则重试或抛出异常。例如 版本号法、CAScompare and swap即乐观锁实现应用场景①适合读多写少(并发冲突的概率低)可以降低锁定带来的性能开销②系统能够容忍或者处理失败的情况因为乐观锁可能会因为并发冲突导致执行失败。》并发性能高悲观锁持悲观态度认为并发冲突的概率高每次操作数据时都加锁确保线程安全。例如 synchronized即悲观锁实现应用场景①适合写多读少(并发冲突的概率高)避免频繁冲突导致的多次重试②数据一致性要求极高如金融系统》并发性能低串行化操作按 synchronized 的优化过程无锁 → 偏向锁JDK15 后已移除→ 轻量级锁 → 重量级锁锁只能升级不能降级。CAS 原理Compare And Swap比较再交换在更新前检查数据是否被修改若未被修改则更新否则自旋重试。具体解释主存中有一个共享变量当线程想要去修改这个变量的值的时候需要先把这个变量的值拷贝到线程的工作内存中然后进行计算操作得到一个新的值。然后线程带着旧值和新值再次找到主存中的共享变量。先比较旧值和现在主存中的共享变量是否相同。若相同说明在这期间没有其他线程修改过主存中的共享变量是线程安全的。因此把新值写入主存的共享变量中。若不同说明在这期间有其他线程修改过主存中的共享变量不是线程安全的。因此此时需要自旋重试。自旋再次取得主存中共享变量的值。回到线程的工作内存进行计算操作得到新值。再次比较旧值和主存中的共享变量的值是否相同。又回到刚刚的逻辑。直至自旋成功。优点不加锁无锁并发保存线程安全。缺点自旋占用CPU。Synchronized原理背景知识每个Java对象头部都有一个MarkWord存储着对象的运行时数据如哈希码、锁状态信息、GC分代年龄等锁状态信息标志了Synchronized的状态①在JVM字节码层面synchronized 对应的是 monitorenter 和 monitorexit 两条字节码指令表示进入临界区告诉JVM在此处需要加锁和解锁。②在JVM逻辑层面JVM内部通过 Monitor监视器来管理线程的竞争和等待每个锁对象都关联一个Monitor内部通过逻辑区域管理线程竞争具体流程当线程执行 monitorenter 时JVM会检查 锁对象 的Monitor。若 Owner 为空线程成为 Owner若 Owner 被占用线程进入 EntryList 阻塞。当线程执行 monitorexit 时JVM释放锁并唤醒 EntryList 中的线程让它们去竞争锁。③在操作系统层面需要从用户态切换到内核态向操作系统底层申请互斥量mutex性能开销很大因此称其为重量级锁。锁升级过程JDK1.6对synchronized进行了优化无锁-偏向锁最初是无锁的状态当一个synchronized代码块被线程首次进入时JVM会将锁标记为偏向锁且会在锁对象头中记录下该线程的ID。如果该线程再次请求同一把锁即重入锁通过比较线程ID它会直接获得锁是为了没有锁竞争且需要重入的场景而引入的。偏向锁-轻量级锁如果有其他线程来请求获取锁此时偏向锁就会被撤销两个线程会通过CAS的方式自旋尝试获取锁获取锁后JVM会将锁标记为轻量级锁。自旋锁原理非常简单如果持有锁的线程能在很短时间内释放锁资源那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态它们只需要等一等自旋等持有锁的线程释放锁后即可立即获取锁这样就避免用户线程和内核的切换的消耗。轻量级锁-重量级锁在轻量级锁状态下继续锁竞争如果成功就成功获取轻量级锁。否则进入锁膨胀阶段没有抢到锁的线程将自旋即不停地循环判断锁是否能够被成功获取。长时间的自旋操作是非常消耗资源的一个线程持有锁其他线程就只能在原地空耗CPU执行不了任何有效的任务这种现象叫做忙等busy-waiting。如果锁竞争情况严重某个达到最大自旋次数的线程会将轻量级锁升级为重量级锁。AQS 原理AQS是多线程同步器他是JUC包中多个组件的底层实现比如说Lock、CountDownLatch都是用到了AQS。从本质上来说AQS实现了两种锁机制分别是排他锁和共享锁。所谓排他锁就是存在多个线程只能有一个线程去获得锁的资源比如Lock中的ReentrantLock重入锁。共享锁称为读锁在同一个时刻允许多个线程同时获得锁的资源比如CountDownLatch。AQS作为互斥锁来说需要解决两个核心问题①互斥变量的设计以及多线程同时更新互斥变量的时候线程的安全性。②未竞争到锁资源的等待以及竞争到锁资源释放锁之后的唤醒。AQS采用了一个int类型的互斥变量state用来记录锁竞争的状态0表示当前没有线程竞争到锁资源而大于等于1表示已经有线程持有锁资源。一个线程来获取锁资源的时候首先会判断state是否等于0也就是它说是无锁状态如果是把state更新为1表示占用到锁而这个过程中多个线程进行这个操作就会导致线程安全性问题因此AQS采用了CAS机制去保证state互斥变量更新的一个原子性未获取到锁的线程通过Unsafe类中的park方法进行阻塞把阻塞的线程利用先进先出的原则去加入到一个双向链表的结构中当获得锁资源的线程在释放锁之后会从双向链表的头部去唤醒下一个等待的线程再去竞争锁。synchronized 与 ReentrantLock 的区别synchronized和ReentrantLock都是可重入的独占锁但实现方式不同。synchronized是 JVM 提供的 Monitor 机制使用简单进入和退出同步块时由 JVM 自动加锁和释放锁ReentrantLock基于 AQS 实现需要手动调用lock()和unlock()。功能上ReentrantLock更丰富它支持公平锁、可中断获取锁、超时获取锁、tryLock()以及通过多个Condition实现多条件等待而synchronized不支持这些高级特性只能通过wait/notify使用一个等待队列。