浅谈JAVA并发之ReentrantLock

网友投稿 224 2022-10-21


浅谈JAVA并发之ReentrantLock

1. 介绍

结合上面的ReentrantLock类图,ReentrantLock实现了Lock接口,它的内部类Sync继承自AQS,绝大部分使用AQS的子类需要自定义的方法存在Sync中。而ReentrantLock有公平与非公平的区别,即'是否先阻塞就先获取资源',它的主要实现就是FairSync与NonfairSync,后面会从源码角度看看它们的区别。

2. 源码剖析

Sync是ReentrantLock控制同步的基础。它的子类分为了公平与非公平。使用AQS的state代表获取锁的数量

abstract static class Sync extends AbstractQueuedSynchronizer {

private static final long serialVersionUID = -5179523762034025860L;

/**

* Performs {@link Lock#lock}. The main reason for subclassing

* is to allow fast path for nonfair version.

*/

abstract void lock();

...

}

我们可以看出内部类Sync是一个抽象类,继承它的子类(FairSync与NonfairSync)需要实现抽象方法lock。

下面我们先从非公平锁的角度来看看获取资源与释放资源的原理

故事就从就两个变量开始:

// 获取一个非公平的独占锁

/**

* public ReentrantLock() {

* sync = new ReentrantLock.NonfairSync();

* }

*/

private Lock lock = new ReentrantLock();

// 获取条件变量

private Condition condition = lock.newCondition();

2.1 上锁(获取资源)

lock.lock()

public void lock() {

sync.lock();

}

static final class NonfairSync extends Sync {

private static final long serialVersionUID = 7316153563782823691L;

// 获取资源

final void lock() {

// 若此时没有线程获取到资源,直接设置当前线程独占访问资源。

if (compareAndSetState(0, 1))

setExclusiveOwnerThread(Thread.currentThread());

else

// AQS的方法

acquire(1);

}

protected final boolean tryAcquire(int acquires) {

// 实现在父类Sync中

return nonfairTryAcquire(acquires);

}

}

AQS的acquire

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

// Sync实现的非公平的tryAcquire

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;

}

尝试获取资源的过程是非常简单的,这里再贴一下acquire的流程

2.2 释放资源

lock.unlock();

public void unlock() {

// AQS的方法

sync.release(1);

}

AQS的release

public final boolean release(int arg) {

if (tryRelease(arg)) {

Node h = head;

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);

return true;

}

return false;

}

release的流程已经剖析过了,接下来看看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的实现也很简单,这里再贴一下release的流程图

2.3 公平锁与非公平锁的区别

公平锁与非公平锁,即'是否先阻塞就先获取资源', ReentrantLock中公平与否的控制就在tryAcquire中。下面我们看看,公平锁的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) {

// (2.3.1)

// sync queue中是否存在前驱结点

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;

}

}

区别在代码(2.3.1)

hasQueuedPredecessors

判断当前线程的前面有无其他线程排队;若当前线程在队列头部或者队列为空返回false

public final boolean hasQueuedPredecessors() {

// The correctness of this depends on head being initialized

// before tail and on head.next being accurate if the current

// thread is first in queue.

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());

}

结合下面的入队代码(enq), 我们分析hasQueuedPredecessors为true的情况:

1.h != t ,表示此时queue不为空; (s = h.next) == null, 表示另一个结点已经运行了下面的步骤(2),还没来得及运行步骤(3)。简言之,就是B线程想要获取锁的同时,A线程获取锁失败刚好在入队(B入队的同时,之前占有的资源的线程,刚好释放资源)

2.h != t 且 (s = h.next) != null,表示此时至少有一个结点在sync queue中;s.thread != Thread.currentThread(),这个情况比较复杂,设想一下有这三个结点 A -> B C, A此时获取到资源,而B此时因为获取资源失败正在sync queue阻塞,C还没有获取资源(还没有执行tryAcquire)。

时刻一:A释放资源成功后(执行tryRelease成功),B此时还没有成功获取资源(C执行s = h.next时,B还在sync queue中且是老二)

时刻二: C此时执行hasQueuedPredecessors,s.thread != Thread.currentThrEHCUEAMHead()成立,此时s.thread表示的是B

private Node enq(final Node node) {

for (;;) {

Node t = tail;

if (t == null) { // Must initialize

if (compareAndSetHead(new Node())) // (1) 第一次初始化

tail = head;

} else {

node.prev = t;

if (compareAndSetTail(t, node)) { // (2) 设置queue的tail

t.next = node; // (3)

return t;

}

}

}

}

Note that 1. because cancellations due to interrupts and timeouts may occur at any time, a true return does not guarantee that some other thread will acquire before the current thread(虚假true). 2. Likewise, it is possible for another thread to win a race to enqueue after this method has returned false, due to the queue being empty(虚假false).

这位大佬对hasQueuedPredecessors进行详细的分析,他文中解释了虚假true以及虚假false。我这里简单解释一下:

1.虚假true, 当两个线程都执行tryAcquire,都执行到hasQueuedPredecessors,都返回true,但是只有一个线程执行compareAndSetState(0, acquires)成功

2.虚假false,当一个线程A执行doAcquireInterruptibly,发生了中断,还没有清除掉该结点时;此时,线程B执行hasQueuedPredecessors时,返回true

以上就是浅谈java并发之ReentrantLock的详细内容,更多关于JAVA并发之ReentrantLock的资料请关注我们其它相关文章!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:python在使用过程中安装库的方法
下一篇:私网用户通过NAT No-PAT访问Internet
相关文章

 发表评论

暂时没有评论,来抢沙发吧~