AQS源码解读(三,初级java面试问的问题


四、锁的获取与释放


ReentrantLock锁的获取分别实现了不可中断获取锁lock,尝试获取锁tryLock(获取成功返回true,获取失败返回false),可中断获取锁lockInterruptibly;锁的释放unlock很简单,直接调用的AQS的模板方法release,不需要区分公平性。

1、ReentrantLock#lock

ReentrantLock#lock调用了sync.lock()Synclock()是一个抽象函数,具体的实现在其子类NonfairSyncFairSync中。

public void lock() {

sync.lock();

}

abstract static class Sync extends AbstractQueuedSynchronizer {

/**

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

  • is to allow fast path for nonfair version.

*/

abstract void lock();

}

(1)ReentrantLock.NonfairSync

ReentrantLock.NonfairSync#lock实现了Sync中的抽象方法lock,其非公平性体现在,一上来就抢锁设置statecompareAndSetState(0, 1),如果抢锁成功就设置当前线程为持有独占锁的线程setExclusiveOwnerThread(Thread.currentThread())

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

}

}

如果一上来抢锁失败则进入AQS的模板方法acquire(1)acquire(1)中还会再进行一次抢锁tryAcquire,抢锁失败才进入AQS队列操作acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。(acquire详解请看拙作《AQS源码解读——从acquireQueued探索独占锁实现原理,如何阻塞?如何唤醒?》

//AbstractQueuedSynchronizer#acquire

public final void acquire(int arg) {

//若没有抢到锁,则进入等待队列

if (!tryAcquire(arg) &&

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

//自己中断自己

selfInterrupt();

}

加锁的通用模板acquire()已经在AQS中实现,子类只需要实现tryAcquire即可。NonfairSync#tryAcquire调用了父类的Sync#nonfairTryAcquire。具体逻辑如下:

  • 查看当前state,是否有线程持有锁,没有state=0则继续抢锁。

  • 有线程持有锁,判断持有锁的线程是否是当前线程,是就重入。

//ReentrantLock.Sync#nonfairTryAcquire

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;

}

(2)ReentrantLock.FairSync

FairSyncNonfairSync的主要区别在于,FairSync不会一上来就抢锁,而是先判断队列中是否有其他线程在等待锁,没有再抢锁。

代码的模板逻辑和NonfairSync类似,区别在于FairSynctryAcquire的实现。

static final class FairSync extends Sync {

private static final long serialVersionUID = -3000897897090466540L;

final void lock() {

acquire(1);

}

/**

  • Fair version of tryAcquire. Don’t grant access unless

  • recursive call or no waiters or is first.

*/

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;

}

}

FairSync的公平性主要体现在tryAcquire判断当前没有线程持有锁时,不会立即去抢锁,而是判断AQS队列中是否有其他线程也在等待锁,没有才去抢锁。如果有线程正持有锁,判断是否是当前线程,是则重入。

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

}

2、ReentrantLock#lockInterruptibly

lock是不可中断锁,lockInterruptibly是可中断锁,其直接调用了AQS的模板方法acquireInterruptibly

public void lockInterruptibly() throws InterruptedException {

sync.acquireInterruptibly(1);

}

acquireInterruptiblydoAcquireInterruptibly代码中分别响应中断抛出异常InterruptedException

acquireInterruptiblydoAcquireInterruptibly详解请看拙作《AQS源码解读——从acquireQueued探索独占锁实现原理,如何阻塞?如何唤醒?》

3、ReentrantLock#tryLock

tryLock尝试获取锁,调用的是sync.nonfairTryAcquire(1),不会涉及到AQS队列操作,获取锁成功返回true,失败返回false。

public boolean tryLock() {

return sync.nonfairTryAcquire(1);

}

tryLock还有一个重载方法,可传入一个超时时长timeout和一个时间单位TimeUnit,超时时长会被转为纳秒级。

public boolean tryLock(long timeout, TimeUnit unit)

throws InterruptedException {

return sync.tryAcquireNanos(1, unit.toNanos(timeout));

}

tryLock(long timeout, TimeUnit unit)直接调用了AQS的模板方法tryAcquireNanos,也具备了响应中断,超时获取锁的功能:

  1. 若一开始获取锁tryAcquire失败则进入AQS同步队列doAcquireNanos

  2. 进入同步队列后自旋1000纳秒,还没有获取锁且判断应该阻塞,则会阻塞一定时长。

  3. 超时时长到线程自动唤醒,再自旋还没获取锁,且判断超时则返回false。

tryAcquireNanos详解请看拙作《AQS源码解读——从acquireQueued探索独占锁实现原理,如何阻塞?如何唤醒?》

4、ReentrantLock#unlock

ReentrantLock释放锁的过程没有区分公平性,调用的是AQS的模板方法release(),其基本逻辑如下:

  1. 释放锁tryRelease

  2. 唤醒后继节点unparkSuccessor。(unparkSuccessor详解请看拙作《AQS源码解读——从acquireQueued探索独占锁实现原理,如何阻塞?如何唤醒?》

//ReentrantLock

public void unlock() {

sync.release(1);

}

//AbstractQueuedSynchronizer

public final boolean release(int arg) {

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

阿里一直到现在。**

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-f0LEzYV8-1710780393675)]
[外链图片转存中…(img-8957K9Xi-1710780393676)]
[外链图片转存中…(img-VGjM3CRw-1710780393676)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
[外链图片转存中…(img-OyDD6I7P-1710780393677)]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值