java多线程与锁


# 锁的本质

锁(Lock)是一种同步机制,用于控制多个线程对共享资源的访问,保证同一时刻只有一个线程可以进入临界区(Critical Section)。

临界区:访问共享资源的代码段。

互斥(Mutex):锁的核心目标是实现互斥访问。

可见性:加锁不仅保证互斥,还保证变量的修改对其他线程可见(结合 synchronized 或 Lock 的内存语义)。

# Java 锁

├── 内置锁(synchronized) → 简单、安全、推荐
├── 显式锁(Lock)
│   ├── ReentrantLock → 可中断、可超时、公平锁
│   ├── ReentrantReadWriteLock → 读写分离
│   └── StampedLock → 乐观读,高性能
└── 其他
    ├── volatile(轻量级同步,不保证原子性)
    └── AtomicInteger 等原子类(CAS 无锁并发)


Java 中常见的锁类型

✅ 1. synchronized 关键字(内置锁 / 监视器锁)

Java 最基础的锁机制,由 JVM 实现。

使用方式:

// 1. 修饰实例方法:锁当前对象 (this)
public synchronized void method() { ... }

// 2. 修饰静态方法:锁类对象 (Counter.class)
public static synchronized void staticMethod() { ... }

// 3. 修饰代码块:指定锁对象
public void block() {
    synchronized (this) {
        // 临界区
    }
}

✅ 特点:

  • 自动获取和释放:进入同步块自动加锁,退出时自动释放(即使抛异常)。
  • 可重入:同一个线程可以多次获取同一把锁。
  • 非公平锁:不保证等待时间最长的线程优先获取锁。
  • JVM 层面优化:JDK 1.6+ 引入了 偏向锁、轻量级锁、自旋锁 等优化,性能大幅提升。


✅ 2. ReentrantLock(显式锁)

主要特点是它的名字所表示的含义——“可重入”。简单来说,如果一个线程已经持有了某个锁,那么它可以再次调用lock()方法而不会被阻塞。

java.util.concurrent.locks.ReentrantLock,是 synchronized 的增强版。

使用方式:

import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();  // 手动加锁 可以在同一个线程中调用多次
        try {
            count++;
        } finally {
            lock.unlock(); // 必须在 finally 中释放
        }
    }
}

✅ 特点:

特性synchronizedReentrantLock
是否可中断lockInterruptibly()
是否可设置超时tryLock(timeout)
是否公平锁❌(默认非公平)✅ 可设置 new ReentrantLock(true)
是否可多次 tryLock
条件变量支持wait/notifyCondition(更灵活)

示例:带超时的锁

if (lock.tryLock(3, TimeUnit.SECONDS)) {
    try {
        // 执行操作
    } finally {
        lock.unlock();
    }
} else {
    // 获取锁失败,做降级处理
}


✅ 3. ReentrantReadWriteLock(读写锁)

适用于 读多写少 的场景。

  • 读锁(共享锁):多个线程可同时获取读锁。
  • 写锁(独占锁):写锁是排他的,写时不允许读或写。
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Cache {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();
    private Map<String, Object> map = new HashMap<>();

    public Object get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public void put(String key, Object value) {
        writeLock.lock();
        try {
            map.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }
}
⚠️ 注意:读锁不能升级为写锁(会导致死锁),应避免在读锁中尝试获取写锁。


✅ 4. StampedLock(Java 8+,高性能读写锁)

ReentrantReadWriteLock 更高效,支持 乐观读(Optimistic Reading)

import java.util.concurrent.locks.StampedLock;

public class Point {
    private double x, y;
    private final StampedLock sl = new StampedLock();

    // 乐观读
    public double distanceFromOrigin() {
        long stamp = sl.tryOptimisticRead();
        double currentX = x, currentY = y;
        if (!sl.validate(stamp)) { // 检查是否被写线程修改
            stamp = sl.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                sl.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }

    // 写锁
    public void move(double deltaX, double deltaY) {
        long stamp = sl.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }
}

优点:乐观读不阻塞写线程,适合读操作非常频繁的场景。



四、锁的底层实现机制(简要)

1. synchronized 的优化(JVM 层面)

JDK 1.6+ 引入了锁升级机制:

锁状态触发条件说明
无锁初始状态
偏向锁同一个线程多次进入减少 CAS 操作
轻量级锁多个线程竞争不激烈使用 CAS 和栈帧锁记录
重量级锁竞争激烈依赖操作系统互斥量(Mutex),性能下降
锁会根据竞争情况自动升级,但不会降级。

2. ReentrantLock 的实现

基于 AQS(AbstractQueuedSynchronizer) 框架:

  • 使用 volatile 变量表示状态(state);
  • 线程竞争失败时进入 同步队列(FIFO) 等待;
  • 支持公平/非公平策略。


五、锁的性能对比(大致)

锁类型加锁开销适用场景
synchronized(无竞争)⚡️ 极低(偏向锁)通用,推荐
synchronized(竞争激烈)⚠️ 较高(重量级锁)
ReentrantLock⚡️ 低(用户态自旋)需要超时、中断、公平性
ReentrantReadWriteLock⚡️ 读快,写慢读多写少
StampedLock⚡️⚡️ 最快(乐观读)高并发读,偶尔写


六、使用锁的注意事项

✅ 最佳实践:

  1. 优先使用 synchronized:简单、安全、JVM 优化充分。
  2. 需要高级功能时用 ReentrantLock:如超时、中断、公平锁。
  3. 读多写少用 ReentrantReadWriteLock 或 StampedLock
  4. 锁的粒度要小:只锁必要的代码段,避免“大锁”。
  5. 避免死锁

❌ 常见错误:

java深色版本// 错误:锁了不同的对象
synchronized(new Object()) { ... } // 每次都是新对象,无效!

// 错误:忘记释放 ReentrantLock
lock.lock();
// 忘记 try-finally


七、总结:Java 锁体系图谱

深色版本Java 锁
├── 内置锁(synchronized) → 简单、安全、推荐
├── 显式锁(Lock)
│   ├── ReentrantLock → 可中断、可超时、公平锁
│   ├── ReentrantReadWriteLock → 读写分离
│   └── StampedLock → 乐观读,高性能
└── 其他
    ├── volatile(轻量级同步,不保证原子性)
    └── AtomicInteger 等原子类(CAS 无锁并发)


✅ 一句话总结:

“synchronized 简单用,ReentrantLock 灵活控; 读多写少读写锁,StampedLock 高并发; 锁住关键资源,线程安全不落空。”

掌握这些锁的原理和使用场景,你就能写出高效、安全的多线程程序。如果你有具体的并发问题,欢迎提供代码,我可以帮你分析锁的使用是否合理。