十一. 重入锁-ReentrantLock原理分析

前言

重入锁,即ReentrantLock,继承于Lock接口,提供锁重入功能。重入锁与不可重入锁的区别在于,重入锁支持已经获取锁的线程重复对锁资源进行获取。

正文

Java中的synchronized关键字可以隐式的支持锁重入功能,考虑如下一个例子。

public class HelloUtil {

    public static synchronized void sayHello() {
        System.out.print("Hello ");
        sayWorld();
    }

    public static synchronized void sayWorld() {
        System.out.println("World");
    }

}

已知访问由synchronized关键字修饰的静态方法时需要先获取方法所在类的Class对象作为锁资源,所以当A线程调用HelloUtilsayHello()方法时,需要获取的锁资源为HelloUtil类的Class对象,此时B线程再调用HelloUtilsayHello()sayWorld()方法时会被阻塞,但是A线程却可以在sayHello()方法中再调用sayWorld()方法,即A线程在已经获取了锁资源的情况下又获取了一次锁资源,这就是synchronized关键字对锁重入的支持。

结合上面的例子,已经对重入锁有了直观的认识,下面将分析ReentrantLock是如何实现重入锁的。ReentrantLock的类图如下所示。

在这里插入图片描述

ReentrantLock有三个静态内部类,其中Sync继承于AbstractQueuedSynchronizer,然后FairSyncNonfairSync继承于Sync,因此SyncFairSyncNonfairSync均是ReentrantLock组件中的自定义同步器,且FairSync提供公平获取锁机制,NonfairSync提供非公平获取锁机制。公平和非公平获取锁机制现在暂且不谈,下面先看一下SyncFairSyncNonfairSync实现了哪些方法,如下所示。

在这里插入图片描述

由上述可知,ReentrantLock默认使用非公平获取锁机制,然后可以在构造函数中根据传入的fair参数决定使用哪种机制。现在先对上面的讨论做一个小节:ReentrantLock是可重入锁,即已经获取锁资源的线程可以重复对锁资源进行获取,ReentrantLock内部有三个自定义同步器,分别为SyncNonfairSyncFairSync,其中NonfairSyncFairSync能分别提供非公平获取锁机制和公平获取锁机制,具体使用哪一种获取锁机制,需要在ReentrantLock的构造函数中指定。

接下来结合NonfairSyncFairSynclock()tryAcquire()方法的源码,对非公平获取锁机制和公平获取锁机制进行说明。

NonfairSynclock()方法如下所示。

final void lock() {
	if (compareAndSetState(0, 1))
		setExclusiveOwnerThread(Thread.currentThread());
	else
		acquire(1);
}

非公平获取锁调用lock()方法时会先将stateCAS方式从0设置为1,设置成功表示竞争到了锁,因此非公平获取锁意味着同时获取锁资源时会存在竞争关系,不能满足先到先获取的原则。如果将stateCAS方式从0设置为1失败时,会调用模板方法acquire(),已知acquire()方法会调用tryAcquire()方法,而NonfairSynctryAcquire()方法会调用其父类SyncnonfairTryAcquire()方法,下面看一下其实现。

final boolean nonfairTryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		if (compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	// 当前线程如果是获取到锁资源的线程,则将state字段加1
	// 当前线程如果不是获取到锁资源的线程,则返回false然后加入同步队列
	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0)
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}

nonfairTryAcquire()方法中主要是对获取锁资源的线程进行判断,如果当前线程就是已经获取到锁资源的线程,那么就会将state加1,因为每次都是将state加1,所以可以重复获取锁资源。

接下来再看一下公平获取锁机制的FairSync的实现,首先FairSynclock()方法会直接调用模板方法acquire(),并已知在acquire()方法中会调用tryAcquire()方法,所以这里直接看FairSynctryAcquire()方法的实现。

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;
}

FairSynctryAcquire()方法与NonfairSync的不同在于当state为0时多了一个hasQueuedPredecessors()方法的判断逻辑,即判断当前的同步队列中是否已经有正在等待获取锁资源的线程,如果有,则返回true因此公平获取锁意味着绝对时间上最先请求锁资源的线程会最先获取锁,以及等待获取锁资源时间最长的线程会最优先获取锁,这样的获取锁机制就是公平的。

现在最后分析一下ReentrantLock的解锁逻辑。无论是非公平获取锁机制还是公平获取锁机制,如果重复对锁资源进行了n次获取,那么成功解锁就需要对锁资源进行n次释放,前(n - 1)次释放锁资源都应该返回falseReentrantLockunlock()方法会直接调用AbstractQueuedSynchronizer的模板方法release(),并已知在release()方法中会调用tryRelease()方法,这里调用的是Sync实现的tryRelease()方法,如下所示。

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;
}

tryRelease()方法中每成功释放一次锁资源,就会将state减1,所以当state为0时,就判断锁资源被全部释放,即释放锁资源成功。

总结

重入锁的定义是:已经获取锁资源的线程可以重复对锁资源进行获取

ReentrantLock是重入锁,内部有三个自定义同步器,分别为SyncNonfairSyncFairSync,其中NonfairSyncFairSync能分别提供非公平获取锁机制和公平获取锁机制,具体使用哪一种获取锁机制,需要在ReentrantLock的构造函数中指定。

非公平锁的定义是:同时获取锁资源时会存在竞争关系,不能满足先到先获取的原则

公平锁的定义是:绝对时间上最先请求锁资源的线程会最先获取锁,以及等待获取锁资源时间最长的线程会最优先获取锁,这样的获取锁机制就是公平的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

樱花祭的约定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值