Synchronized
是 非公平锁,它是 Java 中最常见的同步机制之一。在 Java 中,synchronized
用于保护共享资源,确保同一时刻只有一个线程能访问某一代码块或方法。
1. 公平锁与非公平锁的区别
- 公平锁:线程按照请求锁的顺序获取锁,先请求的线程优先获取锁,不存在“插队”的现象。
- 非公平锁:线程请求锁时,可能会直接尝试获取锁,而不关心锁是否已有等待的线程队列,允许“插队”,这能提高性能,但可能导致某些线程长期得不到锁,出现线程饥饿的现象。
2. synchronized
的底层实现(非公平锁)
Synchronized
在 JVM 中的实现主要依赖于对象的对象头(Object Header)和Monitor机制。下面是它的核心机制与底层原理。
2.1 对象头
每个 Java 对象都包含一个 对象头,对象头中有一个叫做 Mark Word 的部分,这个字段包含了锁的状态信息。根据锁的状态,Mark Word
会动态改变其内容。
锁的状态分为以下几种:
- 无锁(Unlocked):对象没有被锁定。
- 偏向锁(Biased Locking):用于优化没有锁竞争的场景,锁偏向于第一个获取它的线程,直到有其他线程尝试获取锁,才会撤销偏向锁。
- 轻量级锁(Lightweight Locking):线程尝试获取锁时,如果没有其他线程竞争,使用轻量级锁。这个阶段通过CAS(Compare and Swap)操作实现快速加锁和解锁。
- 重量级锁(Heavyweight Locking):当线程竞争加剧时,会升级为重量级锁,进入内核态,通过操作系统的Monitor机制来实现阻塞和唤醒。
2.2 Monitor 机制
Monitor 是 synchronized
关键字的真正实现。每个 Java 对象都有一个隐式的 Monitor。当线程进入 synchronized
代码块时,它会尝试获取该对象的 Monitor。这个 Monitor 由 JVM 底层的操作系统实现,并涉及到线程阻塞和唤醒的机制。
Monitor 的具体实现依赖于平台,在 Linux 系统中,它通常会使用 pthread_mutex 或类似机制来实现。
Monitor 的工作原理如下:
- 锁获取:
- 当线程进入
synchronized
方法或代码块时,首先尝试获取 Monitor。如果当前 Monitor 没有被其他线程持有(即未锁定),线程会成功获取锁,进入临界区。 - 如果 Monitor 已经被其他线程持有,则线程会进入阻塞状态,直到持有锁的线程释放锁。
- 当线程进入
- 锁释放:
- 当持有锁的线程退出
synchronized
代码块或方法时,释放锁。JVM 会唤醒阻塞队列中的一个等待线程,使其获取锁并继续执行。
- 当持有锁的线程退出
3. 非公平锁的表现
synchronized
默认是非公平锁,即:
- 当一个线程释放锁后,等待锁的线程并不一定会按照请求顺序依次获取锁。可能存在后请求的线程“插队”成功获取锁,而先前排队的线程仍然在等待的情况。
- 这通过直接尝试获取锁来提高性能,尤其是在没有大量线程竞争的情况下,可以减少线程的上下文切换。
4. synchronized 的优化
在 JDK 1.6 及其之后,synchronized
引入了多种优化手段,提升了性能,尤其是在锁竞争较少的情况下。主要的优化包括以下几种:
4.1 偏向锁(Biased Locking)
偏向锁是一种锁优化机制,适用于无锁竞争的场景。它的原理是,当一个线程第一次获取锁时,会将锁“偏向”于该线程。如果该线程再次进入锁定的代码区域,不需要执行任何同步操作,只要简单检查是否偏向于当前线程。
偏向锁的释放发生在其他线程试图竞争该锁时,偏向锁会被撤销,并根据竞争情况升级为轻量级锁或重量级锁。
4.2 轻量级锁(Lightweight Locking)
轻量级锁是为了减少重量级锁的使用,避免线程阻塞带来的上下文切换开销。
其原理是:
- 当一个线程获取锁时,它会将对象头中的
Mark Word
拷贝到当前线程的栈帧中,并通过 CAS 操作尝试将对象头中的Mark Word
修改为指向栈中锁记录的指针。如果成功,则表示加锁成功。 - 如果失败,说明存在锁竞争,轻量级锁会膨胀为重量级锁。
轻量级锁适用于竞争不激烈的情况。
4.3 自旋锁(Spin Lock)
自旋锁用于避免线程阻塞和唤醒带来的上下文切换开销。自旋锁的核心思想是,如果一个线程尝试获取锁时发现锁已经被占用,它不会立即阻塞,而是在一定的时间内循环检查锁是否被释放,这个过程被称为“自旋”。
自旋锁适合锁持有时间较短的场景,因为在这种情况下,线程可能很快就能获取到锁,避免了阻塞和上下文切换。
5. synchronized
和 ReentrantLock
的对比
Java 提供了另一种锁机制:ReentrantLock
。它可以显式地控制锁的公平性,并且提供了更多的锁操作灵活性,例如:
ReentrantLock
可以被显式声明为公平锁或非公平锁,公平锁会严格按照线程的请求顺序获取锁,避免“插队”现象。ReentrantLock
提供了条件锁(Condition),可以实现更复杂的等待/通知机制。
相比之下,synchronized
是一种隐式锁,它的非公平性是默认行为,无法通过配置改变。但在简单场景下,synchronized
的使用更加简洁。
6. 总结
synchronized
是非公平锁,它通过对象头中的Mark Word
记录锁状态,并通过偏向锁、轻量级锁和重量级锁的机制来进行加锁与解锁。- 它的底层依赖于 JVM 的 Monitor 实现,利用操作系统的互斥锁机制来实现线程的同步与阻塞。
- 为了提高性能,
synchronized
通过偏向锁、自旋锁和轻量级锁等多种机制进行了优化。 - 非公平锁的特点是提高了性能,但可能会出现线程“饥饿”的情况。