AQS(AbstractQueuedSynchronizer)——源码分析

  • @param arg

  • @return

*/

protected boolean tryAcquire(int arg) {

throw new UnsupportedOperationException();

}

/**

  • 独占式释放同步状态,等待获取同步状态的线程将会有机会获取同步状态

  • @param arg

  • @return

*/

protected boolean tryRelease(int arg) {

throw new UnsupportedOperationException();

}

/**

  • 共享式获取同步状态,返回大于等于0的值,表示获取成功,反正获取失败

  • @param arg

  • @return

*/

protected int tryAcquireShared(int arg) {

throw new UnsupportedOperationException();

}

/**

  • 共享式释放同步状态

  • @param arg

  • @return

*/

protected boolean tryReleaseShared(int arg) {

throw new UnsupportedOperationException();

}

/**

  • 当前同步器释放在独占模式下被线程占用,一般该方法表示释放被当前线程所占

  • @return

*/

protected boolean isHeldExclusively() {

throw new UnsupportedOperationException();

}

总结:

| 方法名称 | 描述 |

| — | — |

| protected boolean tryAcquire(int arg) | 独占式获取同步状态,实现该方法需要查询当前状态并且判断同步状态是否符合预期,然后再进行CAS设置同步状态 |

| protected boolean tryRelease(int arg) | 独占式释放同步状态,等待获取同步状态的线程将会有机会获取同步状态 |

| protected int tryAcquireShared(int arg) | 共享式获取同步状态,返回大于等于0的值,表示获取成功,反正获取失败 |

| protected boolean tryReleaseShared(int arg) | 共享式释放同步状态 |

| protected boolean isHeldExclusively() | 当前同步器释放在独占模式下被线程占用,一般该方法表示释放被当前线程所占 |

同步器提供的模板方法主要分为3类:

  1. 独占式获取与释放同步状态

  2. 共享式获取与释放同步状态

  3. 查询同步队列中的等待线程情况

/**

  • 独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回;

  • 否则,将会进入同步等待队列等待,该方法将会调用重写的tryAcquire(int arg)方法

  • @param arg

*/

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))

selfInterrupt();

}

/**

  • 与acquire方法相同,但是该方法可以响应中断,当前线程未获取到同步状态进入同步队列中

  • 如果当前线程被中断,则该方法抛出InterruptedException异常

  • @param arg

  • @throws InterruptedException

*/

public final void acquireInterruptibly(int arg)

throws InterruptedException {

if (Thread.interrupted())

throw new InterruptedException();

if (!tryAcquire(arg))

doAcquireInterruptibly(arg);

}

/**

  • 在acquireInterruptibly方法的基础上增加了超时限制

  • 如果当前线程在超时时间内未获取到同步状态,则返回false,获取到则返回true

  • @param arg

  • @param nanosTimeout

  • @return

  • @throws InterruptedException

*/

public final boolean tryAcquireNanos(int arg, long nanosTimeout)

throws InterruptedException {

if (Thread.interrupted())

throw new InterruptedException();

return tryAcquire(arg) ||

doAcquireNanos(arg, nanosTimeout);

}

/**

  • 共享式额获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待

  • 与独占式获取的主要区别是在同一时刻可以由多个线程同时获取到同步状态

  • @param arg

*/

public final void acquireShared(int arg) {

if (tryAcquireShared(arg) < 0)

doAcquireShared(arg);

}

/**

  • 与acquireShared方法相同,该方法响应中断

  • @param arg

  • @throws InterruptedException

*/

public final void acquireSharedInterruptibly(int arg)

throws InterruptedException {

if (Thread.interrupted())

throw new InterruptedException();

if (tryAcquireShared(arg) < 0)

doAcquireSharedInterruptibly(arg);

}

/**

  • 在acquireSharedInterruptibly的基础上增加了超时限制

  • @param arg

  • @param nanosTimeout

  • @return

  • @throws InterruptedException

*/

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)

throws InterruptedException {

if (Thread.interrupted())

throw new InterruptedException();

return tryAcquireShared(arg) >= 0 ||

doAcquireSharedNanos(arg, nanosTimeout);

}

/**

  • 独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒

  • @param arg

  • @return

*/

public final boolean release(int arg) {

if (tryRelease(arg)) {

java.util.concurrent.locks.AbstractQueuedSynchronizer.Node h = head;

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

unparkSuccessor(h);

return true;

}

return false;

}

/**

  • 共享式的释放同步状态

  • @param arg

  • @return

*/

public final boolean releaseShared(int arg) {

if (tryReleaseShared(arg)) {

doReleaseShared();

return true;

}

return false;

}

/**

  • 获取等待在同步队列上的线程集合

  • @return

*/

public final Collection getQueuedThreads() {

ArrayList list = new ArrayList();

for (java.util.concurrent.locks.AbstractQueuedSynchronizer.Node p = tail; p != null; p = p.prev) {

Thread t = p.thread;

if (t != null)

list.add(t);

}

return list;

}

总结

| 方法名称 | 描述 |

| — | — |

| void acquire(int arg) | 独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回;否则,将会进入同步等待队列等待,该方法将会调用重写的tryAcquire(int arg)方法 |

| void acquireInterruptibly(int arg) | 与acquire方法相同,但是该方法可以响应中断,当前线程未获取到同步状态进入同步队列中,如果当前线程被中断,则该方法抛出InterruptedException异常 |

| tryAcquireNanos(int arg, long nanosTimeout) | 在acquireInterruptibly方法的基础上增加了超时限制,如果当前线程在超时时间内未获取到同步状态,则返回false,获取到则返回true |

| acquireShared(int arg) | 共享式额获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以由多个线程同时获取到同步状态 |

| void acquireSharedInterruptibly(int arg) | 与acquireShared方法相同,该方法响应中断 |

| tryAcquireSharedNanos(int arg, long nanosTimeout) | 在acquireSharedInterruptibly的基础上增加了超时限制 |

| boolean release(int arg) | 独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒 |

| boolean releaseShared(int arg) | 共享式的释放同步状态 |

| Collection getQueuedThreads() | 获取等待在同步队列上的线程集合 |

2.2 自定义独占锁加强AbstractQueuedSynchronizer的工作原理的理解

独占锁指的是,同一时刻只能有一个线程获取到锁,其他获取锁的线程只能处于等待队列中等待,只有获取到锁的线程释放了锁,后继的线程才能获取到锁。(不太了解的可以写一遍,基本上就懂了)

package com.lizba.p5;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

/**

  •  自定义独占锁
    
  • @Author: Liziba

  • @Date: 2021/6/19 22:40

*/

public class Mutex implements Lock {

private static class Sync extends AbstractQueuedSynchronizer {

/**

  • 尝试获取锁

  • @param arg

  • @return

*/

@Override

protected boolean tryAcquire(int arg) {

// 当前状态如果为0则获取到锁

if (compareAndSetState(0, 1)) {

// 设置锁的占用线程为当线程

setExclusiveOwnerThread(Thread.currentThread());

return true;

}

return false;

}

/**

  • 尝试释放锁

  • @param arg

  • @return

*/

@Override

protected boolean tryRelease(int arg) {

// 如果当前同步状态为0,调用该方法则抛出异常

if (getState() == 0) {

throw new IllegalMonitorStateException();

}

// 清空占用线程

setExclusiveOwnerThread(null);

// 设置共享状态为0

setState(0);

return true;

}

/**

  • 判断当前线程是否处于占用状态

  • @return

*/

@Override

protected boolean isHeldExclusively() {

return getState() == 1;

}

/**

  • 返回一个Condition,每个Condition包含一个condition队列

  • @return

*/

Condition condition() {

return new ConditionObject();

}

}

// 将需要的操作代理至Sync上

private Sync sync = new Sync();

@Override

public void lock() {

sync.acquire(1);

}

@Override

public boolean tryLock() {

return sync.tryAcquire(1);

}

@Override

public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {

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

}

@Override

public void lockInterruptibly() throws InterruptedException {

sync.acquireInterruptibly(1);

}

@Override

public void unlock() {

sync.release(1);

}

@Override

public Condition newCondition() {

return sync.condition();

}

public boolean hasQueuedThreads() {

return sync.hasQueuedThreads();

}

}

总结自定义同步组件Mutex互斥锁:

通过自定义同步组件Mutex我们可以看出。Mutex定义了一个静态内部类,该内部类继承了同步器并实现了独占式获取和释放同步状态。用户在使用Mutex的时候并不会直接和内部同步器打交道,而是调用Mutex提供的方法,在Mutex的实现中以获取锁的lock()方法为例,只需要在方法实现中调用同步器的模板方法acquire(int args)即可。这种实现方法大大降低了实现一个可靠自定义同步组件的门槛。(不多说_Doug Lea牛逼_)

3、AbstractQueuedSynchronizer实现分析

分析的主要内容包括如下几个方面

  1. 同步队列

  2. 独占式同步状态获取与释放

  3. 共享式同步状态获取与释放

  4. 超时获取同步状态

3.1 同步队列

同步队列实现依赖的是内部的一个(FIFO)的同步队列来完成同步状态管理的,而这个队列的重中之重就是AbstractQueuedSynchronizer的内部类Node,这个Node节点是用来保存同步失败的线程引用、等待状态以及前驱prev和后继next节点。

节点源码重点部分解释:

static final class Node {

/**

  • 等待状态

  • 0 初始状态

  • -3 = PROPAGATE 表示下一次共享式同步状态获取将会无条件地传播下去

  • -2 = CONDITION 节点线程等待在Condition上,当其他线程对Condition调用了signal()后,节点将会从等待队列中转移到同步队列中,加入到同步状态的获取中

  • -1 = SIGNAL 当前节点的线程释放同步状态或者被取消,则通知后继节点,使其节点线程得以运行

  • 1 = CANCELLED 同步队列中等待的线程等待超时或者被中断,需要从同步队列中取消,进入该状态后将不会在改变状态

*/

volatile int waitStatus;

/**

  • 前驱节点

*/

volatile Node prev;

/**

  • 后继节点

*/

volatile Node next;

/**

  • 获取同步状态的线程

*/

volatile Thread thread;

/**

  • 等待在Condition上的后继节点。当前节点是共享模式时,含义特殊,它也代表节点类型(独占/共享)

*/

Node nextWaiter;

}

在AbstractQueuedSynchronizer的内部类代表同步队列中的节点,而AbstractQueuedSynchronizer会持有首节点(head)和尾节点(tail),获取同步状态失败的节点加入队列的尾部。

/**

  • AbstractQueuedSynchronizer中持有的同步队列的头节点

*/

private transient volatile Node head;

/**

  • AbstractQueuedSynchronizer中持有的同步队列的尾节点

*/

private transient volatile Node tail;

通过一组图来查看AQS的结构

AQS同步队列的基本结构

  1. 同步器持有首节点和尾节点,初始都为null

  2. 获取同步状态失败的线程节点加入队列尾部

在这里插入图片描述

AQS同步队列加入节点

  1. 通过compareAndSetTail(Node expect, Node update)提供的CAS操作来正确的设置尾节点

  2. 加入尾节点成功后,将原先尾节点的后继节点指向新的尾节点,新的尾节点的前驱节点设置为原尾节点

在这里插入图片描述

AQS同步队列设置首节点

  1. 同步队列节点顺序出入队遵循FIFO

  2. 原先首节点释放同步状态,唤醒后继节点,后继节点获取同步状态成功后设置自己为首节点

在这里插入图片描述

3.2 独占式同步状态获取与释放

独占式同步状态获取通过调用同步器的acquire(int arg)获取同步状态,注意该方法不响应中断。

获取解析

acquire(int arg)源码解析

/**

  • 方法主要完成以下功能

  • 1、线程安全的同步状态获取

  • 2、节点构造加入同步队列

  • 3、同步队列中自旋

*/

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

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

selfInterrupt();

}

acquire(int arg)之addWaiter(Node mode)

/**

  • 尝试将获取同步状态失败的节点通过CAS的方式加入同步队列尾部

*/

private Node addWaiter(Node mode) {

// 创建一个新的节点,设置节点信息,和节点线程为当前线程

Node node = new Node(Thread.currentThread(), mode);

// 获取尾节点,当尾节点不为空的时候,尝试设置尾节点

Node pred = tail;

if (pred != null) {

// 设置当前插入节点的前驱节点为当前同步队列中的尾节点

node.prev = pred;

// 通过CAS快速设置尾节点为当前插入的节点

if (compareAndSetTail(pred, node)) {

// 如设置尾节点成功,将pred节点(同步队列上一个的尾节点,此时新的尾节点为插入节点)的后继节点指向新插入的节点(新的尾节点)

pred.next = node;

// 返回节点对象

return node;

}

}

enq(node);

return node;

}

addWaiter(Node mode)之 enq(node)

/**

  • 通过“死循环”的方式来正确的添加节点

*/

private Node enq(final Node node) {

// 不断循环,直至CAS插入节点成功

for (;😉 {

Node t = tail;

if (t == null) {

// 当尾节点为null,此时需要初始化头节点和尾节点

if (compareAndSetHead(new Node()))

tail = head;

} else {

// 插入节点前驱节点指向原先尾节点

node.prev = t;

// CAS插入至同步队列的尾节点

if (compareAndSetTail(t, node)) {

t.next = node;

return t;

}

}

}

}

acquire(int arg)之 acquireQueued(final Node node, int arg)

/**

  • 死循环获取同步状态,并且当前仅当前驱节点是头节点是才能够尝试获取同步状态

*/

final boolean acquireQueued(final Node node, int arg) {

boolean failed = true;

try {

boolean interrupted = false;

// 不断循环

for (;😉 {

// 获取当前节点的前驱节点,如果前驱节点为null将会抛出空指针异常

final Node p = node.predecessor();

// 如果当前节点的前驱节点是头节点,尝试获取同步状态

if (p == head && tryAcquire(arg)) {

// 设置当前节点为头节点,并且将节点线程和节点的前驱节点置为null,help GC

setHead(node);

p.next = null; // help GC

failed = false;

return interrupted;

}

// 如果不符合条件,则判断当前节点前驱节点的waitStatus状态来决定是否需要挂起LockSupport.park(this);

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} finally {

// 失败则取消

if (failed)

cancelAcquire(node);

}

}

为何当且仅当当前节点的前驱节点是头节点才能尝试获取同步状态?

我们先看一张节点自旋获取同步状态的图,再总结原因

在这里插入图片描述

  1. 头节点是成功获取同步状态的节点,头节点线程释放同步状态之后会唤醒后继节点,后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节点

  2. 保持并维护同步队列FIFO的出队入队原则

acquire(int arg) 执行时序图

在这里插入图片描述

3.2 release

当同步状态获取成功之后,当前线程从acquire(int arg)方法返回,执行响应逻辑执行,需要释放同步状态,是的后续节点能够继续获取同步状态。释放同步状态调用的是release(int arg) 方法,该方法释放同步状态之后,会唤醒其他后继节点。

源码解析:

/**

  • 释放同步状态,并且唤醒后继节点

*/

public final boolean release(int arg) {

// 尝试释放同步状态

if (tryRelease(arg)) {

Node h = head;

// 如果头节点不为空,并且头节点的等待状态waitStatus不为初始状态0,此时判断为仍存在后继节点

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

// 使用LockSupport来唤醒处于等待状态的线程

unparkSuccessor(h);

return true;

}

return false;

}

3.3 独占式同步状态获取与释放总结
  1. 在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并且在队列中进行自旋

  2. 移除队列或停止自旋的条件是当前节点的前驱节点是头节点并且成功的获取到了同步状态

  3. 释放同步状态时调用release(int arg) 来释放同步状态,如果存在后继节点则唤醒后继节点

3.3 共享式同步状态获取与释放

区别

共享式同步状态获取与独占式同步状态获取的主要区别在于同一时刻释放能被多个线程同时获取到同步状态。例如,文件的读写,一个程序对文件进行读操作,那么此时运行多个读操作同步进行,但是写操作将会被阻塞。而独占式访问,例如写操作,此时会阻塞其他所有的读写操作。

共享式和独占式访问资源对比

在这里插入图片描述
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

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

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

说一千道一万,不如自己去行动。要想在移动互联网的下半场是自己占有一席之地,那就得从现在开始,从今天开始,马上严格要求自己,既重视业务实现能力,也重视基础和原理。基础夯实好了,高楼才能够平地而起,稳如泰山。

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

一线互联网面试专题

379页的Android进阶知识大全

379页的Android进阶知识大全

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

droid移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-bcbGQ5b8-1713295253152)]

[外链图片转存中…(img-Ox9EoedQ-1713295253153)]

[外链图片转存中…(img-ldt1piRs-1713295253154)]

[外链图片转存中…(img-12mrXQmj-1713295253155)]

[外链图片转存中…(img-RF4dz8KH-1713295253156)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

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

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

说一千道一万,不如自己去行动。要想在移动互联网的下半场是自己占有一席之地,那就得从现在开始,从今天开始,马上严格要求自己,既重视业务实现能力,也重视基础和原理。基础夯实好了,高楼才能够平地而起,稳如泰山。

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

[外链图片转存中…(img-zlApWQD8-1713295253157)]

[外链图片转存中…(img-EEQochj4-1713295253159)]

[外链图片转存中…(img-BVRsZtel-1713295253160)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值