一.常见的锁策略
1.乐观锁和悲观锁
描述的是加锁时遇到的场景
悲观锁 : 加锁的时候 , 预测接下来的锁竞争会非常激烈 , 就需要针对这种情况进行一些额外的工作
乐观锁 : 加锁的时候 , 预测接下来的锁竞争情况不激烈 , 就不需要额外的工作
注意 : Synchronized 初始使用乐观锁策略 , 但发现竞争比较频繁的时候 , 就会自动切换成悲观锁策略
2.重量级锁和轻量级锁
遇到加锁之后的解决方案

重量级锁 : 加锁机制重度依赖于 OS 的 mutex ; 会有大量的内核态用户切换 , 容易引发线程的调度(一般是用来应对悲观场景下的锁 , 需要付出更多的代价)
轻量级锁 : 加锁机制尽量不再使用 mutex , 而是在用户态代码完成 , 实在搞不定了 , 再使用 mutex ; 会有少量的内核态用户切换 , 不太容易引发线程调度
用户态和内核态 : 是操作系统为权限隔离 , 保障安全而划分的两种 CPU 运行级别, 核心区别在于对硬件/系统资源的访问权限 , 所有进程运行都会在这两种状态之间切换
注意 : synchronized 开始是一个轻量级锁 , 如果锁冲突比较严重 , 就会变成重量级锁
3.挂起等待锁和自旋锁
挂起等待锁 : 获取锁失败后 , 线程放弃 CPU资源 并进入阻塞队列挂起 , 只有锁被释放后才被唤醒(重量级锁的典型实现)
自旋锁 : 获取锁失败后 , 线程不放弃 CPU 并 进入自旋(循环)检测锁是否被释放(轻量级锁的典型实现) (自旋锁的特点是不阻塞线程 , 而是通过循环消耗 CPU 资源来等待锁释放 , 适用于锁竞争不激烈 , 持有时间短的场景)
注意 : synchronized 中的轻量级锁策略大概率是通过自旋锁的方式实现的
总结 :
悲观锁=>重量级锁=>挂起等待锁
乐观锁=>轻量级锁=>自旋锁
synchronzied 针对上述的锁策略是自适应的
4.互斥锁和读写锁
互斥锁 : 任意时刻仅允许一个线程访问临界区(读/写均互斥)
读写锁 : ① 读锁 : 多线程可同时持有(读并发) ; ② 写锁 : 仅一个线程持有(写互斥) ; 可以提高效率 , 减少互斥的机会
Java 标准库中提供了 ReentrantReadWriteLock 类 , 实现了读写锁
- ReenTrantReadWriteLock.ReadLock 类表示一个读锁 , 这个对象提供了 lock/unlock 方法
- ReenTrantReadWriteLock.WriteLock 类表示一个写锁 , 实现了lock/unlock 方法
- 其中 : ① 读加锁和读加锁之间 , 不互斥 ; ② 写加锁和写加锁之间 , 互斥 ; ③ 读加锁和写加锁之间 , 互斥
注意 : synchronized 不是读写锁
5.可重入锁和不可重入锁
可重入锁 : 允许同一线程多次获取同一把锁(递归锁) ; 内部机制 : 维护 [ 线程归属标记+锁计数器 ] , 解锁需要计数器归 0
不可重入锁 : 同一线程持有锁时 , 再次请求锁会阻塞/死锁
在 Java 中只要以 Reentrant 开头命名的锁都是可重入锁 , 而且 JDK 提高的所有现成的 Lock 实现类 , 包括 synchronized 关键字锁都是可重入的 ; 而 Linux 系统中提供的 mutex 时不可重入的
注意 : synchronized 是可重入锁
6.公平锁和非公平锁(针对插队现象的公平和非公平)
公平锁 : 按照线程等待的先后顺序获取锁 , 先等待的线程先执行
非公平锁 : 线程请求锁时直接尝试抢锁 , 抢不到再进入等待队列(机会均等)
Java 标准库中
公平锁 : ReentrantLock(true) ,
非公平锁 : ReentrantLock(false)/synchronized
注意 :
synchronized 是非公平锁
操作系统内部的线程调度就可以视为随机的 , 如果不做任何额外的限制 , 锁就是非公平锁 , 如果想要实现公平锁 , 就需要依赖额外的数据结构 , 来记录线程的先后顺序
二.synchronized 原理
1.核心特性
可重入 , 非公平 , 隐式加锁/解锁(JVM 自动处理)

2.锁升级
无锁 => 偏向锁 => 轻量级锁=> 重量级锁
注意 : 锁升级是单项的(只能从低到高) , 无法降级(不会从重量级到轻量级)
① 无锁
此时没有锁竞争 , 无需加锁
② 偏向锁(Biased Lock) -- 单线程无竞争场景
目标 : 消除单线程重复加锁的开销(仅第一次加锁有少量开销)
- 加锁逻辑 :

- 解锁逻辑 :
![]()
③ 轻量级锁(Lightweight Lock) -- 多线程交替竞争场景
目标 : 用 CAS 自旋代替内核态阻塞 , 减少上下文切换开销
触发条件 : 有多个线程竞争偏向锁 , JVM 撤销偏向锁 , 升级为轻量级锁
- 加锁逻辑 :

- 解锁逻辑 :

④ 重量级锁(Heavuweight Lock) -- 多线程持续竞争场景
目标 : 通过操作系统内核态的监视器锁保证互斥 , 牺牲性能换取稳定性
触发条件 : 轻量级锁自旋次数达到阈值 , 或多个线程同时自旋 , JVM 升级为重量级锁
- 加锁逻辑 :

- 监视器锁 :

3.其他锁优化
1.锁消除(Lock Elimination) - 移除"无竞争的锁"
JVM 的 JIT 编译器通过逃逸分析判断 : 若锁对象是线程私有的 , 则该线程不存在线程并发竞争 , JIT 会在编译阶段自动移除该锁的加锁操作
代码示例 :
① 局部变量作为锁对象
public void lockEliminationDemo() {
// 锁对象:局部变量 lockObj,仅当前线程可见(无逃逸)
Object lockObj = new Object();
synchronized (lockObj) { // JIT 会消除此锁
System.out.println("无竞争的同步代码块");
}
}
优化后 :
public void lockEliminationDemo() {
Object lockObj = new Object();
System.out.println("无竞争的同步代码块"); // 锁被消除
}
②JDK 内置类的隐式锁消除
StringBuffer的append()方法是同步方法(加了synchronized),但如果StringBuffer是局部变量(无逃逸),JIT 会消除锁:
public String stringBufferDemo() {
// sb 是局部变量,无逃逸
StringBuffer sb = new StringBuffer();
sb.append("Java"); // 同步方法,锁被消除
sb.append("Lock"); // 同步方法,锁被消除
return sb.toString();
}
sb仅在当前方法内使用,无其他线程竞争,JIT 消除append()方法的synchronized锁,性能等同于StringBuilder
2. 锁粗化(Lock Coarsening) - 合并细粒度锁
当 JIT 检测到同一个锁对象被频繁,连续地加锁解锁 (如循环内加锁 , 连续调用同步方法) , 会见多次加解锁操作合并为一次 , 减少锁操作对底层的开销
代码示例 :
① 循环内细粒度的锁
'public class demo38 {
private static int count = 0;
public static void main(String[] args) {
Object locker = new Object();
Thread t1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
synchronized (locker){
count++;
}synchronized (locker){
count++;
}synchronized (locker){
count++;
}
}
});
}
}
优化后
public class demo38 {
private static int count = 0;
public static void main(String[] args) {
Object locker = new Object();
Thread t1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
synchronized (locker){
count++;
count++;
count++;
}
}
});
}
}
注意 : JIT 不会无限制的粗化锁 : 若合并后的锁持有时间过长 , 可能导致其他线程长期阻塞 , JIT 会平衡 加解锁次数 和 持有锁时间 , 仅对短时间内连续的锁操作进行粗优化
|
维度 |
synchronized |
ReentrantLock |
|
锁类型 |
隐式锁(JVM 自动管理) |
显式锁(手动 lock ()/unlock ()) |
|
公平性 |
仅非公平锁 |
可配置公平 / 非公平锁 |
|
锁升级 |
自动(无锁→偏向→轻量→重量) |
无锁升级,默认非公平,底层依赖 CAS+AQS |
|
功能扩展 |
无(仅基础加锁 / 解锁) |
支持可中断锁、超时锁、条件变量(Condition) |
三.CAS(Compara And Swap)
CAS (比较和交换)是 Java 实现无锁并发编程的核心底层机制 , 属于乐观锁的实现 , 通过 CPU 原子指令保证操作的原子性 , 无需传统锁(如 synchronized)的阻塞/唤醒开销
1.核心操作
- 一个 CAS 涉及到的操作 : 若内存地址 V 的值 == 预期值 A , 则将 V 更新为 B , 返回 true ; 否则不操作 , 返回 false
- 硬件层面的 CPU 原子指令 , JVM 通过 Unsafe 类调用底层指令 , 保证"比较 - 交换" , 不可中断 ; 当多个线程同时对某个资源进行 CAS 操作时 , 只能有一个线程操作成功 , 但是并不会阻塞其他线程 , 其他线程只会收到操作失败的信号
- check and set
- read and update
一个伪代码
参数 : 内存地址(要操作变量的内存地址) , 预期值(变量的旧值) , 新值
booleanCAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
2.应用
① 是java.util.concurrent.atomic 包
import java.util.concurrent.atomic.AtomicInteger;
public class demo39 {
private static AtomicInteger count = new AtomicInteger(0);//赋初始值为0
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
count.getAndIncrement();//count++;
//count.incrementAndGet();//++count;
//count.addAndGet(4);//count+=4;
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
count.getAndIncrement();//count++;
//count.incrementAndGet();//++count;
//count.addAndGet(4);//count+=4;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count = " +count);
}
}

② 基于 CAS 实现自旋锁
伪代码
public class SpinLock {
private Thread owner = null;
public void lock(){
// 通过 CAS 看当前锁是否被某个线程持有.
// 如果这个锁已经被别的线程持有, 那么就⾃旋等待.
// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
while(!CAS(this.owner, null, Thread.currentThread())){
}
}
public void unlock (){
this.owner = null;
}
}
若 owner 为 null(锁未被持有) , 则将其设为当前线程 ; 若锁已经被其他线程持有 , 则通过 while 循环自旋等待
3.ABA 问题
① 问题描述 :
线程 1 操作是 [初始值A => 经过修改操作值为B] ; 线程 2 的操作是 [数是指为 A => 修改为 C => 又退回 A] ; 此时线程 1 执行 CAS 时 , 检测到变量仍然为 A , 误以为未被修改 , 会再次将其更新为 B
② 问题核心 :
CAS 仅校验最终值 , 忽略中间修改
③ 解决方案 :
引入版本号 , 在 CAS 比较数据当前值和旧值的同时 , 也要比较版本号是否符合预期
当真正修改的时候 , 如果当前版本号 与 读到的版本号相同 , 则修改数据 , 并把版本号+1 , 如果当前版本号高于读到的版本号 , 就操作失败(认为数据已经被修改过了)
4.仅支持单个变量原子操作 问题
① 问题描述 :
CAS 只能保证单个变量操作的原子性 , 无法直接实现多变量的原子更新(如同时更新 a 和 b)
② 解决方案 :
合并变量 : 将多个变量合并为一个对象 , (通过 AtomicReference 操作对象引用);
import java.util.concurrent.atomic.AtomicReference;
class MulNum{
int a;
int b;
public MulNum(int a,int b){
this.a = a;
this.b = b;
}
}
public class demo40 {
private static AtomicReference<MulNum> count = new AtomicReference<>(new MulNum(1,2));
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Thread t1 = new Thread(()->{
while(true){
MulNum oldVar = count.get();
MulNum newVar = new MulNum(oldVar.a + 1, oldVar.b + 1);
if(count.compareAndSet(oldVar,newVar)){
System.out.println("更新后:a=" + newVar.a + ", b=" + newVar.b);
break;
}
}
});
t1.start();
}
}
}
实现互斥锁(ReentrantLock 为例)
import java.util.concurrent.locks.ReentrantLock;
public class demo41 {
private int a = 1;
private int b = 2;
private ReentrantLock lock = new ReentrantLock();
public void updateMultiVar() {
lock.lock();
try {
a++;
b++;
System.out.println("更新后:a=" + a + ", b=" + b);
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
demo41 demo = new demo41();
for (int i = 0; i < 5; i++) {
new Thread(demo::updateMultiVar).start();
}
}
}
872

被折叠的 条评论
为什么被折叠?



