synchronized 是公平锁还是非公平锁

Synchronized非公平锁,它是 Java 中最常见的同步机制之一。在 Java 中,synchronized 用于保护共享资源,确保同一时刻只有一个线程能访问某一代码块或方法。

1. 公平锁与非公平锁的区别

  • 公平锁:线程按照请求锁的顺序获取锁,先请求的线程优先获取锁,不存在“插队”的现象。
  • 非公平锁:线程请求锁时,可能会直接尝试获取锁,而不关心锁是否已有等待的线程队列,允许“插队”,这能提高性能,但可能导致某些线程长期得不到锁,出现线程饥饿的现象。

2. synchronized 的底层实现(非公平锁)

Synchronized 在 JVM 中的实现主要依赖于对象的对象头(Object Header)和Monitor机制。下面是它的核心机制与底层原理。

2.1 对象头

每个 Java 对象都包含一个 对象头,对象头中有一个叫做 Mark Word 的部分,这个字段包含了锁的状态信息。根据锁的状态,Mark Word 会动态改变其内容。

锁的状态分为以下几种:

  1. 无锁(Unlocked):对象没有被锁定。
  2. 偏向锁(Biased Locking):用于优化没有锁竞争的场景,锁偏向于第一个获取它的线程,直到有其他线程尝试获取锁,才会撤销偏向锁。
  3. 轻量级锁(Lightweight Locking):线程尝试获取锁时,如果没有其他线程竞争,使用轻量级锁。这个阶段通过CAS(Compare and Swap)操作实现快速加锁和解锁。
  4. 重量级锁(Heavyweight Locking):当线程竞争加剧时,会升级为重量级锁,进入内核态,通过操作系统的Monitor机制来实现阻塞和唤醒。

2.2 Monitor 机制

Monitor 是 synchronized 关键字的真正实现。每个 Java 对象都有一个隐式的 Monitor。当线程进入 synchronized 代码块时,它会尝试获取该对象的 Monitor。这个 Monitor 由 JVM 底层的操作系统实现,并涉及到线程阻塞和唤醒的机制。

Monitor 的具体实现依赖于平台,在 Linux 系统中,它通常会使用 pthread_mutex 或类似机制来实现。

Monitor 的工作原理如下:

  1. 锁获取
    • 当线程进入 synchronized 方法或代码块时,首先尝试获取 Monitor。如果当前 Monitor 没有被其他线程持有(即未锁定),线程会成功获取锁,进入临界区。
    • 如果 Monitor 已经被其他线程持有,则线程会进入阻塞状态,直到持有锁的线程释放锁。
  2. 锁释放
    • 当持有锁的线程退出 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. synchronizedReentrantLock 的对比

Java 提供了另一种锁机制:ReentrantLock。它可以显式地控制锁的公平性,并且提供了更多的锁操作灵活性,例如:

  • ReentrantLock 可以被显式声明为公平锁非公平锁,公平锁会严格按照线程的请求顺序获取锁,避免“插队”现象。
  • ReentrantLock 提供了条件锁(Condition),可以实现更复杂的等待/通知机制。

相比之下,synchronized 是一种隐式锁,它的非公平性是默认行为,无法通过配置改变。但在简单场景下,synchronized 的使用更加简洁。

6. 总结

  • synchronized 是非公平锁,它通过对象头中的 Mark Word 记录锁状态,并通过偏向锁、轻量级锁和重量级锁的机制来进行加锁与解锁。
  • 它的底层依赖于 JVM 的 Monitor 实现,利用操作系统的互斥锁机制来实现线程的同步与阻塞。
  • 为了提高性能,synchronized 通过偏向锁、自旋锁和轻量级锁等多种机制进行了优化。
  • 非公平锁的特点是提高了性能,但可能会出现线程“饥饿”的情况。
0 0 投票数
Article Rating
订阅评论
提醒
guest
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x