公平锁和非公平锁是多线程编程中对锁获取策略的两种不同实现,主要区别在于锁对等待线程的调度方式。
公平锁
公平锁(Fair Lock)遵循 先来先服务 的原则,线程按照请求锁的顺序依次获取锁。
特点
-
排队机制:
-
线程请求锁时,如果锁被占用,会进入一个等待队列。
-
当锁被释放时,队列中等待最久的线程优先获得锁。
-
-
避免线程饥饿:
-
公平锁可以防止某些线程长时间无法获取锁的问题。
-
-
性能较低:
-
排队和唤醒线程的开销较高,可能导致整体性能下降。
-
实现方式
在 Java 的 ReentrantLock
中,通过将构造方法的参数 fair
设置为 true
来启用公平锁:
ReentrantLock lock = new ReentrantLock(true);
示例
import java.util.concurrent.locks.ReentrantLock; public class FairLockExample { private static final ReentrantLock lock = new ReentrantLock(true); public static void main(String[] args) { Runnable task = () -> { for (int i = 0; i < 2; i++) { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " got the lock"); } finally { lock.unlock(); } } }; 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(); } }
运行结果通常会按照线程的启动顺序依次获取锁,例如:
Thread-1 got the lock
Thread-2 got the lock
Thread-3 got the lock
...
非公平锁
非公平锁(Non-Fair Lock)是默认的锁实现,线程尝试直接竞争锁,不关注排队顺序。
特点
-
竞争机制:
-
每个线程都可以直接尝试获取锁,成功则进入临界区,失败则进入等待队列。
-
-
性能较高:
-
由于不需要维护严格的排队顺序,线程调度和唤醒的开销较小,吞吐量更高。
-
-
可能导致线程饥饿:
-
某些线程可能长时间无法获取锁,因为新来的线程可能会插队成功。
-
实现方式
在 Java 的 ReentrantLock
中,通过将构造方法的参数 fair
设置为 false
或默认值启用非公平锁:
ReentrantLock lock = new ReentrantLock(false);
示例
import java.util.concurrent.locks.ReentrantLock; public class NonFairLockExample { private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { Runnable task = () -> { for (int i = 0; i < 2; i++) { lock.lock(); try { System.out.println(Thread.currentThread().getName() + " got the lock"); } finally { lock.unlock(); } } }; 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(); } }
运行结果可能会出现线程抢占的情况,例如:
Thread-1 got the lock
Thread-3 got the lock
Thread-2 got the lock
Thread-3 got the lock
...
公平锁 vs 非公平锁
特性 | 公平锁 | 非公平锁 |
---|---|---|
锁分配顺序 | 按线程请求锁的顺序分配 | 随机分配,允许插队 |
线程饥饿 | 不会发生 | 可能发生 |
性能 | 性能较低,开销较大 | 性能较高,吞吐量大 |
使用场景 | 需要严格控制线程公平性 | 更关注性能,允许一定的不公平性 |
选择建议
-
使用公平锁的场景:
-
需要避免线程饥饿,例如在多线程的资源分配中需要确保所有线程都能公平参与竞争。
-
-
使用非公平锁的场景:
-
更注重性能,线程竞争较少时(如大部分操作锁很快释放),非公平锁的性能优势更明显。
-
默认情况下,ReentrantLock
使用非公平锁,因为在大多数场景下性能优先于严格的公平性。