Java中的公平锁和非公平锁实现详解

 

在Java中实现锁的方式有两种,一种是使用Java自带的关键字synchronized对相应的类或者方法以及代码块进行加锁,另一种是ReentrantLock,前者只能是非公平锁,而后者是默认非公平但可实现公平的一把锁。

ReentrantLock的实现是基于其内部类FairSync(公平锁)NonFairSync(非公平锁)实现的。 其可重入性是基于Thread.currentThread()实现的: 如果当前线程已经获得了执行序列中的锁, 那执行序列之后的所有方法都可以获得这个锁。

在本文将集中对ReentrantLock以及它实现公平锁和非公平锁的方式进行讲解:

首先我们来看一段ReentrantLock的源代码,有助于我们等会对非公平和公平锁的理解:

 

public class ReentrantLockExample {
	int a=0;
	ReentrantLock lock= new ReentrantLock();
	public void write() {
		lock.lock();                // 获取锁
		try {
			a++;
		}finally  {
			lock.unlock();          // 释放锁
		}
	}
	
	public void reader() {
		lock.lock();                // 获取锁
		try {
			int i=a;
			...
		} finally {
			lock.unlock();          // 释放锁
		}
	}
}

ReentrantLock的实现依赖于Java同步器框架AbstractQueuedSynchronizer(AQS)。AQS使用一个整形的volatile变量state来维护同步状态,这个volatile变量是实现ReentrantLock的关键。我再看一下下面ReentrantLock的类图

公平锁:

公平和非公平锁的队列都基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。公平锁则在于每次都是依次从队首取值。
锁的实现方式是基于如下几点: 
表结点Node和状态state的volatile关键字。
sum.misc.Unsafe.compareAndSet的原子操作。

非公平锁:

在等待锁的过程中, 如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的。
ReentrantLock锁都不会使得线程中断,除非开发者自己设置了中断位。 
ReentrantLock获取锁里面有看似自旋的代码,但是它不是自旋锁。 
ReentrantLock公平与非公平锁都是属于排它锁。

ReentrantLock的可重入性分析


 不过考虑到基础知识不一,如果对锁的概念不太清晰的也可参考我这篇博客   jvm---静态方法加锁和非静态方法加锁的区别 

ReentrantLock的可重入性

前言里面提到,ReentrantLock重入性是基于Thread.currentThread()实现的: 如果当前线程已经获得了锁, 那该线程下的所有方法都可以获得这个锁。ReentrantLock的锁依赖只有 NonfairSync和FairSync两个实现类, 他们的锁获取方式大同小异。
 

else if

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();         // 获取锁的开始,首先读volatile变量
    if (c == 0) {               // 这个if条件是没有其他线程获取该对象的锁,我们用CAS尝试获取锁
       if(ifFirst(current) && compareAndSetState(0,acquires)){
            setExclusiveOwnerThread(current);
            return(true)        // 这个方法在cas成功后,将对象的Mark Word里的锁标志改成该线程
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        // 是当前线程,直接获取到锁。实现可重入性。
        int nextc = c + acquires;    // acquires默认传入1,相当于让nextc自增
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);             // 将标志位改成自增后的值
        return true;
    }
    return false;
}

此处有两个值需要关心:

 

/**
      * The current owner of exclusive mode synchronization.
      * 持有该锁的当前线程
      */
     private transient Thread exclusiveOwnerThread;     -----------------两个值不在同一个类----------------
    /**
      * The synchronization state.
      * 0: 初始状态-无任何线程得到了锁
      * > 0: 被线程持有, 具体值表示被当前线程持有的执行次数
      * 
      * 这个字段在解锁的时候也需要用到。
      * 注意这个字段的修饰词: volatile
      */
     private volatile int state;

 

公平锁FairSync

公平锁的实现机理在于每次有线程来抢占锁的时候,都会检查一遍有没有等待队列,如果有, 当前线程会执行如下步骤:

if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires))
{ setExclusiveOwnerThread(current);
return true; }

其中hasQueuedPredecessors是用于检查是否有等待队列的。 

  

public final boolean hasQueuedPredecessors() {
         Node t = tail; // Read fields in reverse initialization order
         Node h = head;
         Node s;
         return h != t &&
             ((s = h.next) == null || s.thread != Thread.currentThread());
     }

 非公平锁NonfairSync

非公平锁在实现的时候多次强调随机抢占: 

if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true; }
}

与公平锁的区别在于新晋获取锁的进程会有多次机会去抢占锁。如果被加入了等待队列后则跟公平锁没有区别。 

ReentrantLock锁的释放

ReentrantLock锁的释放是逐级释放的,也就是说在 可重入性 场景中,必须要等到场景内所有的加锁的方法都释放了锁, 当前线程持有的锁才会被释放! 
释放的方式很简单, state字段减一即可:

protected final boolean tryRelease(int releases) {
     //  releases = 1
     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;
 }