在Java中,ReentrantLock 提供了公平锁和非公平锁两种模式。它们的加锁流程有所不同,主要体现在线程获取锁的顺序上。下面我会用 流程图文字说明 来详细解释这两种锁的加锁流程。


1. 公平锁(Fair Lock)

公平锁的特点是按照线程请求锁的顺序来分配锁,即先到先得。它的加锁流程如下:

流程说明:

  1. 线程尝试获取锁。
  2. 如果锁空闲,检查是否有其他线程在等待队列中:
    • 如果没有等待队列,当前线程直接获取锁。
    • 如果有等待队列,当前线程加入队列尾部,等待被唤醒。
  3. 如果锁被占用,当前线程加入等待队列尾部,等待被唤醒。
  4. 当锁释放时,队列中的第一个线程会被唤醒并尝试获取锁。

2. 非公平锁(Non-Fair Lock)

非公平锁的特点是线程可以直接尝试获取锁,而不考虑等待队列的顺序。它的加锁流程如下:

流程说明:

  1. 线程尝试获取锁。
  2. 如果锁空闲,直接尝试获取锁:
    • 如果获取成功,线程继续执行。
    • 如果获取失败(例如被其他线程抢先),线程加入等待队列尾部。
  3. 如果锁被占用,线程直接加入等待队列尾部,等待被唤醒。
  4. 当锁释放时,队列中的线程会被唤醒并尝试获取锁,但新来的线程也可以直接尝试获取锁,可能导致“插队”现象。

3. 公平锁 vs 非公平锁的区别

特性 公平锁 非公平锁
获取锁的顺序 严格按照等待队列的顺序(先到先得) 允许插队,新线程可能直接获取锁
性能 较低,因为需要维护队列顺序 较高,减少了线程切换的开销
适用场景 需要严格公平性的场景 对性能要求较高的场景

4. 代码示例

以下是使用 ReentrantLock 的公平锁和非公平锁的示例:

import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    public static void main(String[] args) {
        // 公平锁
        ReentrantLock fairLock = new ReentrantLock(true);
        // 非公平锁
        ReentrantLock nonFairLock = new ReentrantLock(false);

        Runnable task = () -> {
            fairLock.lock(); // 或 nonFairLock.lock()
            try {
                System.out.println(Thread.currentThread().getName() + " 获取锁");
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                fairLock.unlock(); // 或 nonFairLock.unlock()
                System.out.println(Thread.currentThread().getName() + " 释放锁");
            }
        };

        Thread t1 = new Thread(task, "Thread-1");
        Thread t2 = new Thread(task, "Thread-2");
        Thread t3 = new Thread(task, "Thread-3");

        t1.start();
        t2.start();
        t3.start();
    }
}

5. 总结

  • 公平锁:保证线程获取锁的顺序,但性能较低。
  • 非公平锁:允许线程插队,性能较高,但可能导致某些线程长时间等待。

根据具体场景选择合适的锁模式,可以更好地平衡公平性和性能。