各种锁

乐观锁、悲观锁
这不是一种具体的锁,是一个概念,可以认为所有的锁都是悲观或乐观的,java 中绝大部分锁都是悲观锁(synchronized、ReentrantLock等);也有乐观锁(原子类的递增、读写锁的读锁),java 的乐观锁都是 CAS 实现的
悲观锁
每次线程对资源做任何操作都要先获取锁,哪怕所有的线程都是读操作
synchronized、Lock 的实现类、数据库 update、delete 、select 加 for update 等都是悲观锁。特点是万无一失,但损失性能
乐观锁
每次线程都不加锁,如果是是写操作,回写数据的时候要进行判断有没有别的线程修改过,如果有被修改就重试或报错
数据库的 version 机制,CAS 都是乐观锁的体现。特点是当读操作多的时候性能非常好
偏向锁
- 第一个线程获取到锁后,把锁对象的 对象头的 markdown 区域写上自己的线程 id
- 对象头有哪个线程id,就表示这个对象被哪个线程持有锁
- 后续如果有重入或竞争先对比线程 id,如果 id 一致,没有竞争,一直是偏向锁
后续如果有竞争,升级为自旋锁(等待的线程一直 CAS 尝试把自己的线程 id 写到 markdown 里),如果竞争激烈升级为重量级锁
自旋锁、轻量级锁
两种叫法是同一个东西,是乐观锁,java 的自旋锁是 CAS 实现的,整个过程中没有加锁、释放锁的步骤(虽然很多人也叫无锁,但是并不合适)
原理
变量 flag 的初始值为 1,线程 A 要做 flag++
- 线程 A 保存一个 flag 的副本,此时 flag = 1(每个线程有自己的栈空间)
- 计算 a+1 后的值,是 2
- 计算完成,要修改了,修改先再读取一次 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 的实现类 都是可重入锁
读写锁
- 也叫共享锁、排他锁/独占锁,是指锁的分类,而不是一个单独的锁,类似数据库的 S 和 X 锁
- 读锁是共享的,多个线程可以同时获取;写锁只能一个线程获取
- 当资源的写锁被持有时,资源的读锁其他线程是获取不到的
锁升级、锁膨胀、锁优化
写在 synchronized 章中
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具