美团技术文档 - 线程锁总结

出于保护数据同步的目的,在多线程操作时对于共享资源需要引入锁的概念,锁的选型会直接的影响系统的性能。

1. 互斥锁

what: 让线程互斥的访问共享资源,同一时间只会有一个线程能对该资源进行访问

why: 对于线程1 写操作;线程2 读的操作,如果同时进行,读到的可能就不是最新的值

how: 
	- 两种状态: (locked)状态 & (unlocked)状态
	- 加锁(acquire):当互斥锁处于未加锁状态时,则加锁成功(把锁设置为已加锁状态),并返回;当互斥锁处于已加锁状态时,那么试图对它加锁的线程会被阻塞,直到该互斥量被解锁。
	- 解锁(release):通过把锁设置为未加锁状态释放锁,其他因为申请加锁而陷入等待的线程,将获得执行机会。如果有多个等待线程,只有一个会获得锁而继续执行。

notice: 在访问资源前申请锁访问 - 操作后 - 释放锁

2. 读写锁

what: 与互斥锁一直,多了一种状态,加读锁、加写锁和释放锁

why:说白了就是读锁的状态下不能写锁,反之一致,在有读锁的情况,如果还有读的行为可以继续读,写锁就不可以

how:读写锁适合对共享资源访问“读大于写”的场合。读写锁也叫“共享互斥锁”,多个读线程可以并发访问同一资源,这对应共享的概念,而写线程是互斥的,写线程访问资源的时候,其他线程无论读写,都不可以进入临界代码区。

notice: 一般写的权重会高于读,如果一直有线程来读数据,读锁一直被占用,那么写锁会“饿死”。

3. 自旋锁

what: 线程在acquire自旋锁失败的时候,不会主动让出CPU从而进入睡眠等待状态,而是会一直忙等占着CPU,然后更快的准备测试和设置指令,直到成功获得执行,因为自旋锁会期待线程持有锁后快速释放锁。

why: 内核态线程的部分线程不能睡眠,需要持续去持有资源,例如中断函数等;用户态的线程则不推荐了,因为等待过程属于浪费资源,因为用户态的线程并不能保证很快的release锁,此处的资源浪费就违背了自旋锁的概念。

notice: 自旋锁必须在多核架构下,否则无论什么态,一直占有所有线程都等待,全“饿死”了。

锁的范围

尽可能小的范围,减少持有时间

死锁

ABBA锁

死锁场景.png
时序图
死锁时序.png

HOW:

--

public class ABBADeadlock {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        // 线程A: 先A后B
        Thread threadA = new Thread(() -> {
            synchronized (lockA) {
                System.out.println("线程A: 持有锁A");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                System.out.println("线程A: 尝试获取锁B...");
                synchronized (lockB) {  // ❌ 阻塞在这里
                    System.out.println("线程A: 获取锁B成功");
                }
            }
        });

        // 线程B: 先B后A(ABBA顺序!)
        Thread threadB = new Thread(() -> {
            synchronized (lockB) {
                System.out.println("线程B: 持有锁B");
                try { Thread.sleep(100); } catch (InterruptedException e) {}
                
                System.out.println("线程B: 尝试获取锁A...");
                synchronized (lockA) {  // ❌ 阻塞在这里
                    System.out.println("线程B: 获取锁A成功");
                }
            }
        });

        threadA.start();
        threadB.start();
    }
}

解决方案

Snipaste_2026-02-03_10-58-30.png
// 修正后的线程B:也按A→B顺序
Thread threadBFixed = new Thread(() -> {
    synchronized (lockA) {        // ✅ 先获取A
        synchronized (lockB) {    // ✅ 再获取B
            // 安全执行
        }
    }
});

场景

加锁顺序

结果

ABBA

A→B vs B→A

死锁

AABB

A→B vs A→B

安全

自死锁

对于不支持重复加锁的锁,如果线程持有某个锁,而后又再次申请锁,因为该锁已经被自己持有,再次申请锁必然得不到满足,从而导致死锁。