Java公平锁和非公平锁实现原理
在Java中,锁的实现主要通过java.util.concurrent.locks
包下的Lock
接口及其实现类(如ReentrantLock
)来完成。这些锁提供了比synchronized
关键字更灵活的锁定机制,包括公平锁(Fair Lock)和非公平锁(Non-fair Lock)。
1. 公平锁与非公平锁的区别
公平锁(Fair Lock):公平锁会按照请求锁的顺序来依次获得锁,即按照线程请求锁的顺序来分配锁,保证了线程调度的公平性。在ReentrantLock
中,可以通过构造函数指定是否为公平锁,例如:
Lock fairLock = new ReentrantLock(true); // 创建公平锁
非公平锁(Non-fair Lock):非公平锁在尝试获取锁时会直接尝试,而不是按照请求的顺序,这通常会导致某些线程可能会更快地获得锁,但这也可能导致某些线程饥饿(即长时间无法获取锁)。在ReentrantLock
中,如果不指定为公平锁,则默认为非公平锁:
Lock unfairLock = new ReentrantLock(); // 默认创建非公平锁
2. 实现原理
非公平锁
非公平锁的实现通常依赖于CAS(Compare-And-Swap)操作或者通过一个简单的循环尝试来获取锁。当线程尝试获取锁时,它会检查锁的状态:
-
如果锁当前未被占用,线程会尝试立即获取锁。
-
如果锁已被其他线程占用,线程会直接进入等待状态,或者在下次调度时再次尝试获取。
这种实现方式简单且高效,但可能导致某些线程饥饿。
公平锁
公平锁的实现通常涉及到更多的调度逻辑,以确保线程按照请求的顺序获取锁。在ReentrantLock
中,公平锁的实现通常涉及到维护一个队列(如AbstractQueuedSynchronizer
中的队列),线程在请求锁时会先尝试将自身加入到等待队列的末尾,然后按照队列中的顺序来获取锁。具体步骤如下:
-
线程请求锁时,首先检查是否有线程在等待队列中。
-
如果有,则当前线程会被放置在队列的末尾。
-
持有锁的线程释放锁后,会从队列头部取出下一个线程(即队列中最先等待的线程),并允许其获取锁。
3. 代码示例
非公平锁示例:
Lock lock = new ReentrantLock(); // 默认非公平锁
lock.lock();
try {// 临界区代码
} finally {lock.unlock();
}
公平锁示例:
Lock fairLock = new ReentrantLock(true); // 创建公平锁
fairLock.lock();
try {// 临界区代码
} finally {fairLock.unlock();
}
通过使用ReentrantLock
,我们可以根据需要选择公平或非公平的锁定策略,以优化多线程应用性能和资源分配策略。