各种锁

乐观锁、悲观锁

这不是一种具体的锁,是一个概念,可以认为所有的锁都是悲观或乐观的,java 中绝大部分锁都是悲观锁(synchronized、ReentrantLock等);也有乐观锁(原子类的递增、读写锁的读锁),java 的乐观锁都是 CAS 实现的

悲观锁

每次线程对资源做任何操作都要先获取锁,哪怕所有的线程都是读操作

synchronized、Lock 的实现类、数据库 update、delete 、select 加 for update 等都是悲观锁。特点是万无一失,但损失性能

乐观锁

每次线程都不加锁,如果是是写操作,回写数据的时候要进行判断有没有别的线程修改过,如果有被修改就重试或报错

数据库的 version 机制,CAS 都是乐观锁的体现。特点是当读操作多的时候性能非常好

偏向锁

  1. 第一个线程获取到锁后,把锁对象的 对象头的 markdown 区域写上自己的线程 id
  2. 对象头有哪个线程id,就表示这个对象被哪个线程持有锁
  3. 后续如果有重入或竞争先对比线程 id,如果 id 一致,没有竞争,一直是偏向锁

后续如果有竞争,升级为自旋锁(等待的线程一直 CAS 尝试把自己的线程 id 写到 markdown 里),如果竞争激烈升级为重量级锁

自旋锁、轻量级锁

两种叫法是同一个东西,是乐观锁,java 的自旋锁是 CAS 实现的,整个过程中没有加锁、释放锁的步骤(虽然很多人也叫无锁,但是并不合适)

原理

变量 flag 的初始值为 1,线程 A 要做 flag++

  1. 线程 A 保存一个 flag 的副本,此时 flag = 1(每个线程有自己的栈空间)
  2. 计算 a+1 后的值,是 2
  3. 计算完成,要修改了,修改先再读取一次 flag 的值
    • 如果主存中 flag = 1,发现和自己的副本值是相等的,允许修改,回写主存,主存 flag 的值设置为 2
    • 如果主存中 flag != 1,说明和自己副本的值不相等了,不允许修改,自旋或报错

ABA 问题

线程 A 读取主存的值是1,认为没有被修改,如果主存中的值被别的线程修改 2,然后又改为 1呢?

可以加个版本号解决解决,JUC 下的 AtomicStampedReference 内部维护了一个 Pair 对象(里面又版本信息)用于解决在原子类的 ABA 问题

更深层次的问题

线程 A 对比完值 和 回写新的值,这个必须保证原子性,刚对比完是允许修改的,正要回写还没回写的时候,别的线程修改了?

要做的就是对比和回写这个两个指令不能被打断,也就是期间不允许别的线程访问,原理是给这两条指令上了锁的,具体怎么证明?证明个毛线哦,java 调用 c,c 调用 汇编,有能力的自己去翻吧

重量级锁

用户态、内核态,这是操作系统的两种运行模式,不同模式有不同命令,内核态的命令一般程序不能直接调用执行,需要经过操作系统,操作系统执行这些高级别的命令。JVM 是已用户态的模式在运行,想要加一把重量级锁,这个只能用户态跟操作系统发起申请,操作系统接收到指令然后调用内核态的指令,这个开销是比较大的

比如 java 不能直接操作内存,只能间接通过 UnSafe 类访问,比如前端 js 不能操作系统文件,再比如普通程序不能直接修改操作系统内核结构等等

公平锁、非公平锁

多个线程获取锁时,按照先来后到的方式获取,就是是公平锁,先到先得;非公平锁就是后排队但是可能先得到锁(插队)

公平锁

线程一启动就进入 FIFO 队列,严格按照先后顺序获取到锁。等持有锁的线程释放锁然后唤醒队列中第一个线程,唤醒线程是需要开销的,所以相对非公平锁来说吞吐量低一些

非公平锁

线程一启动,先不着急进入队列,先尝试捡漏,先获取一下看能不能获取到(可能刚好别的线程释放锁),如果获取不到再进入队列。如果刚好那么凑巧,每次新来的线程都能捡漏成功,队列里的线程就没机会被唤醒造成锁饥饿

重入锁、不可重入锁

在一个方法调用链中,如果外层方法获取了锁,内层方法自动也获取到该锁(前提是同一个锁,就不用再次获取锁),这就是可重入锁,反之就是不可重入锁,不可重入锁很容易造成死锁

public class Widget {
    // doSomething 获取到锁
    public synchronized void doSomething() {
        System.out.println("方法1执行...");
        // 如果是不可重入锁,doOthers 就要等 doSomething 释放锁,但是 doSomeing 没有执行完不会释放
        doOthers();
    }
    public synchronized void doOthers() {
        System.out.println("方法2执行...");
    }
}

目前 jdk1.8 没有不可重入锁,synchronized、Lock 的实现类 都是可重入锁

读写锁

  1. 也叫共享锁、排他锁/独占锁,是指锁的分类,而不是一个单独的锁,类似数据库的 S 和 X 锁
  2. 读锁是共享的,多个线程可以同时获取;写锁只能一个线程获取
  3. 当资源的写锁被持有时,资源的读锁其他线程是获取不到的

锁升级、锁膨胀、锁优化

写在 synchronized 章中

posted @   CyrusHuang  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示