我们知道实现一把锁要有如下几个逻辑:
我们在讲解AQS的时候说过AQS基本负责了实现锁的全部逻辑,唯独线程抢锁和线程释放锁的逻辑是交给子类来实现了,而ReentrantLock作为最常用的独占锁,其内部就是包含了AQS的子类实现了线程抢锁和释放锁的逻辑。
我们在使用ReentrantLock的时候一般只会使用如下方法:
ReentrantLock lock=new ReentrantLock();
lock.lock();
lock.unlock();
lock.tryLock();
Condition condition=lock.newCondition();
condition.await();
condition.signal();
condition.signalAll();
如果我们自己来实现一个锁,那么如何设计呢?
根据AQS的逻辑,我们写一个子类sync,这个类一定会调用父类的acquire方法进行上锁,同时重写tryAcquire方法实现自己抢锁逻辑,也一定会调用release方法进行解锁,同时重写tryRelease方法实现释放锁逻辑。
那么ReentrantLock是怎么实现的呢?
ReentrantLock的实现的类架构如下,ReentrantLock对外提供作为一把锁应该具备的api,比如lock加锁,unlock解锁等等,而它内部真正的实现是通过静态内部类sync实现,sync是AQS的子类,是真正的锁,因为这把锁需要支持公平和非公平的特性,所以sync又有两个子类FairSync和NonfairSync分别实现公平锁和非公平锁。
因为是否公平说的是抢锁的时候是否公平,那两个子类就要在上锁方法acquire的调用和抢锁方法tryAcquire的重写上做文章。
公平锁做了什么文章?
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
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;
}
}
公平锁比较简单,直接调用了父级类AQS的acquire方法,因为AQS的锁默认就是公平的排队策略。
重写tryAcquire方法的逻辑为:
公平就公平在老老实实排队。
非公平锁做了什么文章?
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
//nonfairTryAcquire代码在父类sync里面
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;
}
非公平锁也很简单,没有直接调用了父级类AQS的acquire方法,而是先通过cas抢锁,它不管等待队列中有没有其他线程在排队,直接抢锁,这就体现了不公平。
它重写tryAcquire方法的逻辑为:
公平锁和非公平分别重写了tryAcquire方法,来满足公平和非公平的特性。那么tryAcquire方法也是需要子类重写的,因为它和是否公平无关,因此tryAcquire方法被抽象到sync类中重写。
sync类中
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;
}
释放锁的逻辑如下:
释放锁往往和抢锁逻辑是对应的,每个子类抢锁逻辑不同的话,释放锁的逻辑也会对应不同。
接下来我们通过ReentrantLock的使用看下它的源码实现:
class X {
private final ReentrantLock lock = new ReentrantLock();
Condition condition1=lock.newCondition();
Condition condition2=lock.newCondition();
public void m() {
lock.lock();
try {
if(条件1){
condition1.await();
}
if(条件2){
condition2.await();
}
} catch (InterruptedException e) {
} finally {
condition1.signal();
condition2.signal();
lock.unlock();
}
}
}
ReentrantLock类
public void lock() {
sync.lock();
}
NonfairSync 类中
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
FairSync 类中
final void lock() {
acquire(1);
}
公平锁和非公平锁中都实现了lock方法,公平锁直接调用AQS的acquire,而非公平锁先抢锁,抢不到锁再调用AQS的acquire方法进行上锁
进入acquire方法后的逻辑我们就都知道了。
public void unlock() {
sync.release(1);
}
unlock方法内直接调用了AQS的Release方法进行解锁的逻辑,进入release方法后逻辑我们都已经知道了,这里不再往下跟。
这个方法中直接调用了sync中的nonfairTryAcquire方法,这个就是非公平锁实现的TryAcquire方法,只是名字改了而已。
tryLock方法的意思是尝试抢锁,是给程序员调用的尝试抢锁方法,如果抢锁成功返回true,抢锁失败返回false,当拿到抢锁失败的信号后,程序员可以做一些自己的策略。
如果没有tryLock方法,而是直接调用lock方法,就会走到acquire方法中,Acquire方法中也会调用TryAcquire方法,只不过在这里如果获取不到锁就会入队阻塞了,就没有程序员参与的可能了。
在AQS那篇我们说过Condition是AQS中的条件队列,可以按条件将一批线程由不可唤醒变为可唤醒。
ReentrantLock类
public Condition newCondition() {
return sync.newCondition();
}
sync静态内部类
final ConditionObject newCondition() {
return new ConditionObject();
}
sync提供了创建Condition对象的方法,意味着ReentrantLock也拥有Condition的能力。
我们下面说的ReentrantLock其实就是说AQS,因为它的同步实现主要在AQS里面。
优化前的synchronized性能很差,主要表现在两个方面:
ReentrantLock是在jdk实现的,它申请互斥量就是对锁标识state的争夺,它是通过cas方式实现。在java端实现。
对于争夺不到资源的线程依然要阻塞挂起,但凡阻塞挂起都要依赖于操作系统底层,这一步的用户态到内核态的切换是避免不了的。
因此在单线程进入代码块的时候,效率是很高的,因此我们说ReentrantLock性能高于原始的synchronized:
两个都是常用的典型的独占锁。
synchronized不需要手动释放锁,ReentrantLock需要手动释放锁,需要考虑异常对释放锁的影响避免异常导致线程一直持有锁。
以下是两个锁的使用方式:
class X {
private final ReentrantLock lock = new ReentrantLock();
Condition condition1=lock.newCondition();
Condition condition2=lock.newCondition();
public void m() {
lock.lock();
try {
if(1==2){
condition1.await();
}
if(1==3){
condition2.await();
}
} catch (InterruptedException e) {
} finally {
condition1.signal();
condition2.signal();
lock.unlock();
}
}
}
class X {
private final testtest sync=new testtest();;
public void m() throws InterruptedException {
synchronized(sync){
if(1==2){
sync.wait();
}
sync.notify();
sync.notifyAll();
}
}
}
对比代码及特性说明:
网页名称:被问到ReentrantLock,你真的能答好吗?
当前URL:http://www.shufengxianlan.com/qtweb/news33/414483.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联