本文将从底层原理和源代码层面详细解释Java的锁机制,尽量用通俗易懂的语言让初学者也能理解。本文会从概念开始,逐步深入到实现细节,涵盖Java锁的种类、底层原理、源码分析,并提供完整的步骤和推导。
锁(Lock)就像现实生活中房间的门锁:当多个人(线程)同时想进入同一个房间(访问共享资源,比如一个变量或对象)时,锁确保只有一个人能进去,其他人得在门外等着。这样可以避免混乱,比如防止两个人同时修改一个银行账户余额导致数据错误。
在Java中,锁是用来解决多线程并发访问共享资源时可能出现的数据不一致问题。简单说,锁是多线程编程中保证线程安全的工具。
假设有两个线程同时操作一个共享变量 counter = 0,每个线程都想执行 counter++。表面上看,counter++ 是一条指令,但实际上它包含三个步骤:
如果没有锁,两个线程可能交错执行这些步骤:
结果是,执行了两次 counter++,但 counter 还是 1,而不是期望的 2。这就是线程不安全的表现。锁通过限制同一时间只有一个线程能执行这些步骤,解决了这个问题。
Java提供了多种锁机制,主要分为以下几类:
我们重点讲解synchronized 和 ReentrantLock,因为它们是Java锁的核心实现。
synchronized 是Java内置的锁机制,可以用来修饰方法或代码块。以下是两种常见用法:
public synchronized void increment() {
counter++;
}
这表示整个方法是线程安全的,同一时间只有一个线程能执行这个方法。
public void increment() {
synchronized(this) {
counter++;
}
}
这表示只有被 synchronized 包裹的代码块是线程安全的,锁的对象是 this。
synchronized 的实现依赖于Java对象中的监视器(Monitor)。每个Java对象都可以作为一个锁,因为对象头中包含一个Monitor结构。
在JVM(Java虚拟机)中,每个对象都有一个对象头,包含以下信息:
如果需要详细了解对象头的内容见我这篇文章:Java的对象头:原理与源码详解
当一个线程尝试获取 synchronized 锁时,JVM会检查对象的 Mark Word,看它是否已经关联了一个 Monitor。Monitor 是一个操作系统级别的互斥锁(Mutex),用来实现线程的互斥访问。
Monitor 有三个核心状态:
用生活中的例子解释:
如果需要详细了解Monitor 的机制见我这篇文章::Java 的 Monitor 机制:原理与源码详解
以下是 synchronized 锁的详细步骤:
虽然 synchronized 是JVM内置的,但它的核心实现在JVM的C++代码中,位于 HotSpot JVM 的源码中。我们可以看看关键部分(简化版,非完整源码)。
在HotSpot JVM中,ObjectMonitor 类负责实现Monitor的功能,位于 src/hotspot/share/runtime/objectMonitor.cpp。以下是核心逻辑的伪代码解释:
class ObjectMonitor {
private:
Thread* _owner; // 当前持有锁的线程
int _recursions; // 重入次数(支持可重入锁)
ObjectWaiter* _entry_list; // 等待锁的线程队列
ObjectWaiter* _wait_set; // 等待notify的线程集合
public:
void enter(Thread* thread) {
// 尝试获取锁
if (_owner == nullptr) {
_owner = thread; // 无人持有,当前线程获取锁
} else if (_owner == thread) {
_recursions++; // 已持有,支持重入
} else {
// 锁被占用,线程加入_entry_list等待
thread->park(); // 线程阻塞
}
}
void exit(Thread* thread) {
// 释放锁
if (_recursions > 0) {
_recursions--; // 重入次数减1
} else {
_owner = nullptr; // 释放锁
notify_waiters(); // 唤醒_entry_list中的线程
}
}
void wait(Thread* thread) {
// 线程调用wait,进入_wait_set
_wait_set->add(thread);
exit(thread); // 释放锁
thread->park(); // 线程阻塞
}
void notify() {
// 唤醒_wait_set中的一个线程
if (_wait_set != nullptr) {
Thread* t = _wait_set->remove_one();
t->unpark(); // 唤醒线程
}
}
};
在Java代码中,synchronized 会被编译成字节码指令 monitorenter 和 monitorexit。例如:
synchronized(obj) {
counter++;
}
编译后的字节码(简化):
monitorenter // 获取锁
iload counter
iadd 1
istore counter
monitorexit // 释放锁
JVM对 synchronized 做了大量优化,提高性能:
这些优化通过对象头的 Mark Word 动态切换锁状态(无锁 → 偏向锁 → 轻量级锁 → 重量级锁)。
ReentrantLock 是 java.util.concurrent.locks 包中的显式锁,提供比 synchronized 更灵活的功能,比如公平锁、超时锁等。
示例:
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int counter = 0;
public void increment() {
lock.lock(); // 获取锁
try {
counter++;
} finally {
lock.unlock(); // 释放锁
}
}
}
ReentrantLock 基于 AQS(Abstract Queued Synchronizer) 框架实现。AQS 是Java并发包的核心,提供了基于队列的同步器,用于管理线程的竞争和等待。
AQS维护以下核心组件:
用生活中的例子解释:
++
;否则加入等待队列。以下是 ReentrantLock 的核心源码(简化版,基于JDK 8):
public class ReentrantLock implements Lock {
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync(); // 默认非公平锁
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
}
static final class NonfairSync extends Sync {
void lock() {
if (compareAndSetState(0, 1)) // CAS尝试获取锁
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1); // 失败则进入AQS队列
}
}
static final class FairSync extends Sync {
void lock() {
acquire(1); // 公平锁直接进入AQS逻辑
}
}
}
ReentrantLock 的锁逻辑依赖 AQS 的 acquire() 和 release() 方法:
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
private volatile int state; // 锁状态
private transient volatile Node head; // 等待队列头
private transient volatile Node tail; // 等待队列尾
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 唤醒下一个线程
return true;
}
return false;
}
}
相比 synchronized,ReentrantLock 提供了更多功能:
特性 | synchronized | ReentrantLock |
---|---|---|
实现方式 | JVM内置,基于Monitor | Java类库,基于AQS |
性能 | JDK 6后优化,性能接近ReentrantLock | 灵活,可优化为公平/非公平锁 |
灵活性 | 简单,只能锁方法或代码块 | 支持中断、超时、条件变量等 |
可重入性 | 支持 | 支持 |
公平性 | 非公平 | 可选公平/非公平 |
使用场景 | 简单场景,代码简洁 | 复杂场景,需要高级功能 |
无论是 synchronized 还是 ReentrantLock,最终都依赖操作系统的互斥锁(Mutex)或条件变量。JVM通过以下方式与操作系统交互:
state
和 CLH队列管理锁状态,CAS和 park/unpark
实现高效同步。