看啥推荐读物
专栏名称: 暖爸2
程序员,奶爸,公众号:暖爸的java家园
今天看啥  ›  专栏  ›  暖爸2

java并发编程之深入学习Concurrent包(四,可重入ReentrantLock锁)

暖爸2  · 简书  ·  · 2020-01-22 22:44

引言:

上一章,我们学习了AQS同步队列的使用,了解了同步队列如何进行线程同步及线程唤醒,及同步队列的运行。这章,让我们一起来看下同步队列的时间应用--ReentrantLock(可重入锁)类

本章基于上一章AQS的内容实现,如果对AQS有不理解,请先阅读上一章内容


简介:

可重入锁,表示同一线程如果已经抢占到锁,那么可以直接重复进入被锁的代码而不需要做额外的操作。

ReentrantLock不仅是可重入锁,且支持公平锁与非公平锁两种方式,公平锁表示新加入的线程无视同步队列的存在而抢占锁,非公平锁则需要先进入同步队列等待,让同步队列中的线程先抢占锁。

实现分析:

1,同步状态值

ReentrantLock锁的实现,首先基于AQS中的同步状态值state,该值表示锁被重入的次数,如果锁未被抢占,state=0,重入一次则state+1。

/**

* The synchronization state.

*/

private volatile int state;


2,ReentrantLock的初始化实现

如下代码所示,默认ReentrantLock是实现非公平锁,只有传入true构造时,才能创建公平锁。

/**

*/

public ReentrantLock() {

sync = new NonfairSync();

}

/**

*/

public ReentrantLock(boolean fair) {

sync = fair ? new FairSync() : new NonfairSync();

}


3,AQS的虚拟子类的实现

如下图所示,ReentrantLock中先定义了一个虚拟子类,定义了非公平锁加锁及释放锁的方法。

3.1 非公平锁nonfairTryAcquire方法

步骤1,获取当前同步器同步状态c

步骤2(如果锁未被抢占,c=0),则直接抢占锁

步骤3(如果锁已被抢占但是当前线程所占),将c+抢占次数,并更新同步器同步状态。

步骤4,非以上两种情况,则直接返回失败。

3.2 释放锁tryRelease公用方法

步骤1,获取同步器同步状态c=当前状态值-释放锁次数

步骤2,如果c=0,表示锁完全释放,则返回true,否则返回false。


abstract static class Sync extends AbstractQueuedSynchronizer {

private static final long serialVersionUID = -5179523762034025860L;

abstract void lock();


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 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类使用AQS类中的同步状态判断可重入锁的状态。

4,非公平锁的特殊实现

可以看出,非公平锁锁定时,直接使用CAS方法试图抢占锁,成功则更新为锁所有线程,失败调用 acquire 进入同步器处理,在同步器处理中再次调用tryAcquire尝试获取锁。后续详细步骤见上一章。

/**

* Sync object for non-fair locks

*/

static final class NonfairSync extends Sync {

private static final long serialVersionUID = 7316153563782823691L;


/**

* Performs lock.  Try immediate barge, backing up to normal

* acquire on failure.

*/

final void lock() {

if (compareAndSetState(0, 1))

setExclusiveOwnerThread(Thread.currentThread());

else

acquire(1);

}


protected final boolean tryAcquire(int acquires) {

return nonfairTryAcquire(acquires);

}

}

5,公平锁的特殊实现

如下图所示,公平锁实现了自己的tryAcquire方法,与非公平锁的最主要区别在于公平锁当前线程只有同步队列为空,同步队列中当前线程在头节点时,才可以直接抢占锁,否则需要进入同步队列进行排队。

static final class FairSync extends Sync {

private static final long serialVersionUID = -3000897897090466540L;


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;

}

}

从上面代码可以了解,公平非公平锁,在于抢占锁时当前线程是否具有优待。


以上就是ReentrantLock类的主要内容,其实了解了AQS之后,这块内容就比较轻松了。

整个Concurrent包中的Lock已经差不多了,明天是最后一篇读写重入锁,后续开始学习Concurrent包中其他内容

Concurrent包


感兴趣的同学们请关注本人公众号:暖爸的java家园




原文地址:访问原文地址
快照地址: 访问文章快照