简单的来说,如果一个线程组里,能保证每个线程都能拿到锁,那么这个锁就是公平锁。相反,如果保证不了每个线程都能拿到锁,也就是存在有线程饿死,那么这个锁就是非公平锁。
一、引入概念
1、公平锁:
多个线程按照申请锁的顺序去获得锁,线程会直接进⼊队列去排队,永远都是队列的第⼀位才能得到锁。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列⾥⾯除了第⼀个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的
开销会很⼤。
2、⾮公平锁:
多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进⼊等待队列,如果能获取到,就直接获取到锁。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会⾼点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:可能导致队列中间的线程⼀直获取不到锁或者⻓时间获取不到锁,导致饿死。
二、Java中的实现
如何能保证每个线程都能拿到锁呢,队列FIFO是一个完美的解决方案,也就是先进先出,java的ReenTrantLock也就是用队列实现的公平锁和非公平锁。 在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中。而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以,它们的差别在于非公平锁会有更多的机会去抢占锁。
1、公平获取锁
java.util.concurrent.locks.ReentrantLock$FairSync.java
protected final boolean tryAcquire( int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//状态为0,说明当前没有线程占有锁
if (c == 0 ) {
//如果当前线程是等待队列的第一个或者等待队列为空,则通过cas指令设置state为1,当前线程获得锁
if (isFirst(current) &&
compareAndSetState( 0 , acquires)) {
setExclusiveOwnerThread(current);
return true ;
}
}
//如果当前线程本身就持有锁,那么叠加状态值,持续获得锁
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0 )
throw new Error( "Maximum lock count exceeded" );
setState(nextc);
return true ;
}
//以上条件都不满足,那么线程进入等待队列。
return false ;
}
2、非公平获取锁
java.util.concurrent.locks.ReentrantLock$Sync.java
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//如果当前没有线程占有锁,当前线程直接通过cas指令占有锁,无视等待队列,就算自己排在队尾也是这样
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
三、适用场景
更多的是直接使用非公平锁:非公平锁比公平锁性能高5-10倍,因为公平锁需要在多核情况下维护一个队列,如果当前线程不是队列的第一个无法获取锁,增加了线程切换次数。