【JUC源码】ReentrantLock源码分析(公平锁与非公平锁)

从类注释可以得到的信息有:

  1. 可重入互斥锁,和 synchronized 锁具有同样的功能语义,但更有扩展性;
  2. 构造器接受 fairness 的参数,fairness 是 ture 时,保证获得锁时的顺序,false 不保证;
  3. 公平锁的吞吐量较低,获得锁的公平性不能代表线程调度的公平性;
  4. tryLock() 无参方法没有遵循公平性,是非公平的(lock 和 unlock 都有公平和非公平,而 tryLock 只有公平锁,所以单独拿出来说一说)。

补充一下第二点,ReentrantLock 的公平和非公平,是针对获得锁来说的,如果是公平的,可以保证同步队列中的线程从头到尾的顺序依次获得锁,非公平的就无法保证,在释放锁的过程中,我们是没有公平和非公平的说法的

1.结构

Reentrantlock 继承关系,核心成员变量及主要构造函数:

public class ReentrantLock implements Lock, java.io.Serializable {
    
    
    
    // Sync 同步器提供了所有的加锁,释放锁的机制
    private final Sync sync;
    
    // Sync 继承了 AQS,使用了 AQS 的 state 字段代表当前锁的计算
    abstract static class Sync extends AbstractQueuedSynchronizer{
    
    ...}   
    // 非公平锁,继承了Sync
    static final class NonfairSync extends Sync{
    
    ...}        
    // 公平锁,继承了Sync
    static final class FairSync extends Sync{
    
    ...}
    
    //------------------------------构造函数-------------------------------------
    // 无参数构造器,即默认是非公平锁
    public ReentrantLock() {
    
    
        sync = new NonfairSync();
    }
	
	// 根据传入的参数决定是公平锁还是非公平锁
    public ReentrantLock(boolean fair) {
    
    
        sync = fair ? new FairSync() : new NonfairSync();
    }
}

1.1 Lock接口

Lock接口,定义了锁的相关方法

public interface Lock {
    
    
    // 上锁 
    void lock();
    // 可中断锁
    void lockInterruptibly() throws InterruptedException;
    
    // 尝试获取锁
    boolean tryLock();
    // 设定尝试时间,过期返回
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    
    // 释放锁
    void unlock();
    
    // 创建条件队列
    Condition newCondition();
}

1.2 Sync

Sync直接继承AQS,提供了无论公平/非公平锁都必须的方法。比如锁状态相关方法,锁释放,获取条件队列等。

abstract static class Sync extends AbstractQueuedSynchronizer {
    
    
        private static final long serialVersionUID = -5179523762034025860L;
		
		// 给子类(FairSync,NonfairSync)实现
        abstract void lock();
        
        //.....
}		

1.2.1 state相关方法

锁的基础state字段,直接调用AQS的方法

 // 锁是否被持有
final boolean isLocked() {
    
    
    return getState() != 0;
}
// 是否是当前线程持有锁
protected final boolean isHeldExclusively() {
    
    
    // 该方法在AbstractOwnableSynchronizer中定义
    return getExclusiveOwnerThread() == Thread.currentThread();
}		
// 持有锁的线程是谁
final Thread getOwner() {
    
    
    return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 线程持有同一个锁的数量
final int getHoldCount() {
    
    
    return isHeldExclusively() ? getState() : 0;
}

1.2.2 nonfairTryAcquire()

尝试获得非公平锁,分为以下三种情况:

  • 锁未被占有,尝试自己拿锁
  • 锁已被自己获取,重入,state+acquire(有上限)
  • 拿锁失败,进入同步队列

这里需要注意一点,公平锁与非公平锁的tryAcquire方法是不同的,非公平锁的tryAcquire在NonfairSync中,但的核心逻辑(nonfairTryAcquire)在Sync中实现,而公平锁的tryAcquire完全是在FairSync中实现。

final boolean nonfairTryAcquire(int acquires) {
    
    
    final Thread current = Thread.currentThread();
    // 获取锁状态
    int c = getState();
    // 同步器的状态是 0 表示同步器的锁没有人持有
    if (c == 0) {
    
    
        // 当前线程,尝试获取锁(CAS修改state)
        if (compareAndSetState(0, acquires)) {
    
    
            // 修改成功,拿到锁,标记当前持有锁的线程是谁
            setExclusiveOwnerThread(current);
            return true;
        }
     }
    // 锁已经被占有,且刚好是当前线程,这里表重入
    else if (current == getExclusiveOwnerThread()) {
    
    
        // 体现可重入性。当前线程持有锁的数量 + acquires
        int nextc = c + acquires;
        // int 是有最大值的,<0 表示持有锁的数量超过了 int 的最大值
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 重新设置state,该放在AQS中定义
        setState(nextc);
        return true;
    }
    // 拿锁失败,线程进入同步队列
    return false;
}

从这个方法中我们还可以看到 Reentrantlock 是可重入的。可重入性说的是线程可以对共享资源重复加锁,对应的,释放时也可以重复释放。

  • 对于 ReentrantLock 来说,在获得锁的时候,state 会加 1,重复获得锁时,不断的对 state 进行递增即可,比如目前 state 是 4,表示线程已经对共享资源加锁了 4 次
  • 线程每次释放共享资源的锁时,state 就会递减 1,直到递减到 0 时,才算真正释放掉共享资源。

1.2.3 tryRelease()

尝试释放锁,非公平和公平锁都的释放都是该方法。也是分为三种情况:

  • 当前线程不持有锁,报错
  • state=0,将owner置为null,表示释放成功
  • state!=0,表示是重入锁且没释放完,return false
 protected final boolean tryRelease(int releases) {
    
    
    // 当前同步器的状态减去释放的个数,参数releases一般为 1
    int c = getState() - releases;
    // 当前线程根本都不持有锁,报错
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果 c 为 0,表示当前线程持有的锁都释放了
    if (c == 0) {
    
    
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 如果 c 不为 0,那么就是可重入锁,并且锁没有释放完,用 state 减去 releases 即可,无需做其他操作
    setState(c);
    return free;
}

1.2.4 newCondition()

创建一个条件队列

final ConditionObject newCondition() {
    
    
    return new ConditionObject();
}

下面就看看非公平锁NonFairSync与公平锁FairSync的差异到底在哪里,何为公平何为非公平。

1.3 非公平锁:NonfairSync

非公平锁的非公平体现在两处:

  1. 线程未进入同步队列就有机会获取到锁:在 lock 方法和 tryAcquire 方法一共有2次尝试修改state去获得锁
  2. 线程已进入同步队列非队二有机会获取到锁:tryAcquire 不会校验当前线程在同步队列的位置(这里可以对比下面FairSync的 tryAcquire 方法),谁都有机会修改state去获得锁。

说的更直接点就是,新线程或阻塞时间短可能比阻塞很久的线程先运行。

	static final class NonfairSync extends Sync {
    
    
        private static final long serialVersionUID = 7316153563782823691L;
		
		// 获取非公平锁
        final void lock() {
    
    
        	// cas给state赋值
            if (compareAndSetState(0, 1))
            	// cas赋值成功,代表拿到当前锁,记录拿到锁的线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
            	// acquire是AQS的方法,会再一次尝试获得锁,若还失败就会进入到同步队列中
                acquire(1);
        }
		
		// 尝试获取非公平锁。该方法会在lock拿锁失败后,在acquire方法中调用
        protected final boolean tryAcquire(int acquires) {
    
    
        	// 直接调用父类Sync的nonfairTryAcquire方法
            return nonfairTryAcquire(acquires);
        }
    }

1.4 公平锁:FairSync

公平锁的公平体现在:

  1. 新线程必须进入同步队列,接收同步器调度
  2. 在同步队列中,只有队二线程才有机会运行

说的更直接点就是,无论新老线程,都必须遵守FIFO谁先阻塞谁先运行。

	static final class FairSync extends Sync {
    
    
        private static final long serialVersionUID = -3000897897090466540L;
		
		// 获取公平锁
		// 相较于非公平锁的lock方法,没了一进来就尝试修改state
        final void lock() {
    
    
        	// acquire 是 AQS 的方法,表示先尝试获得锁,失败之后进入同步队列阻塞等待
            acquire(1);
        }
		
		// 尝试获取非公平锁
		// 相较于非公平锁的nonfairTryAcquire,这里是必须进入了同步队列才有机会修改state拿锁
        protected final boolean tryAcquire(int acquires) {
    
    
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
    
    
            	// hasQueuedPredecessors 是实现公平的关键!!
		        // 它会判断当前线程是不是属于同步队列的头节点的下一个节点(头节点是释放锁的节点)
		        // 如果是(返回false),符合先进先出的原则,可以获得锁
		        // 如果不是(返回true),则继续等待
                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;
        }
    }

这里小结一下,公平性和非公平指的线程得到锁的机制(lock & tyrAcquire),释放锁时没有差异(tryRelease)。如果同步队列中的线程按照阻塞的顺序得到锁,我们称之为公平的,反之是非公平的。

公平的底层实现是 ReentrantLock 的 tryAcquire 方法(调用的是 AQS 的 hasQueuedPredecessors 方法)里面实现的,要释放同步队列的节点时(或者获得锁时),判断当前线程节点是不是同步队列的头节点的后一个节点,如果是就释放,不是则不能释放,通过这种机制,保证同步队列中的线程得到锁时,是按照从头到尾的顺序的。

2.方法 & api

Reentrantlock 中的方法其实没什么说的,因为都是基于AQS框架,具体在这里体现为直接调用Sync

2.1 加锁:lock

public void lock() {
    
    
    sync.lock();
}

2.2 尝试拿锁:tryLock

// 无参构造器
public boolean tryLock() {
    
    
    return sync.nonfairTryAcquire(1);
}
// timeout 为超时的时间,在时间内,仍没有得到锁,会返回 false
public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    
    
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

2.3 释放锁:unlock

public void unlock() {
    
    
    sync.release(1);
}

2.4 条件队列:newCondition

 public Condition newCondition() {
    
    
        return sync.newCondition();
}

3.Reentrantlock与synchronized区别

  • synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
    • sync在优化之前是直结调用os函数实现阻塞,但优化后:volatile+自旋+CAS在用户态实现了线程安全
    • reentrantlock通过AQS在代码级别(volatile+自旋+CAS)实现了线程控制,保证了线程安全
  • synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放
  • synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果(tryLock)
  • synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间(tryLock(timeUnit.S)
  • synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁(new Reentrantlock(true))
  • synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法(newCondition)

猜你喜欢

转载自blog.csdn.net/weixin_43935927/article/details/108721781