一、锁的公平性含义
所谓公平锁,就是在绝对时间上,先对锁发起获取请求的一定先被满足,那么这个锁是公平的,反之,则是非公平的。因为ReentrantLock的实现是通过自定义的静态内部类sync实现的,sync继承了AbstractQueuedSynchronizor抽象类,因此ReentrantLock也是实现了基于双向链表的同步队列,也就是说,如果每次都是选择队列头的Node关联的线程获取锁,那就是公平锁,而非公平锁中,队列中只要有线程妄图获取锁,一般都是很容易成功的,这种任意线程尝试获取锁成功成为插队,非公平锁允许插队。
ReentrantLock可以实现了公平锁(默认非公平锁),而synchronized只能实现非公平锁。
二、公平锁和非公平锁的实现
在ReentrantLock中我们可以找到公平锁和非公平锁获取和释放锁的实现
1、获取锁
非公平锁:为了实现ReentrantLock,因此在获取锁的时候需要首先获取锁的状态(计数器状态),如果锁没有被占用(c==0),那么任意线程可以获取同步状态,如果获取成功就把当前线程设置为独占该锁的线程。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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; //如果所已经被占用,且当前线程不是占用锁的线程,则获取失败
}
公平锁:公平锁获取锁要稍微复杂一点,在获得锁的状态以后,如果锁没有被占用,那么要判断当前线程节点有没有前驱节点,如果没有前驱节点说明该节点是头结点,这时才让当前节点的线程去获取锁;其它逻辑和非公平锁是一样的。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
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、释放锁:
公平锁和非公平锁在释放锁上的操作时一样的,需要使用一个boolean值拉力表示锁是否被完全释放,每次调用tryRelease()方法就会使锁关联的计数器减小,直到计数器状态值为0 ,说明锁被完全释放
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
三、公平锁和非公平锁的比较
○公平锁和非公平锁都是排他锁(具有独占性);
○公平锁机制按照FIFO原则,因此线程“饥饿”出现的概率小很多,而非公平锁只要线程成功获取同步状态就成功获取锁,在这个前提下,成功获取锁又释放锁的线程再次获得锁的概率非常大,这样就很容易造成线程“饥饿”。
○公平锁机制没有非公平锁机制高效,因为公平锁为了保证按照FIFO原则获取锁,因此需要以大量的切换作为代价,而非公平锁切换的次数远远少于公平锁。