刚刚看完了并发实践这本书,算是理论具备了,看到了AQS的介绍,再看看源码,发现要想把并发理解透还是很难得,花了几个小时细分析了一下把可能出现的场景尽可能的往代码中去套,还是有些收获,但是真的很费脑,还是对多线程的理解太浅了,不多说了,直接上代码吧。

这段代码不是为跑通,只是把AQS,ReentrantLock中的部分源码合并到了一起,便于理解。

 package com.yb.interview.concurrent;

 import java.util.concurrent.locks.LockSupport;

 public class AQSSourceStudy {

     abstract static class AQS {
/**
* 这个状态是有子类来维护的,AQS不会用这个状态做什么
*/
private volatile int state;
/**
* 队尾节点
*/
private volatile Node tail;
/**
* 可能情况
*/
private volatile Node head;
/**
* 独占线程
*/
private Thread exclusiveOwnerThread; /**
* 由子类实现
* 判断当前线程是否需要排队
*/
abstract boolean tryAcquire(int i); public int getState() {
return state;
} public void setState(int state) {
this.state = state;
} /**
* 主方法
* 可能的情况
* 当前状态可以直接运行
* 当前状态要放入队列里等待
* 状态->子类获取
* 过程,尽可能的不要去阻塞,循环多次,竞争多次
* 创建节点
* 节点入队,队尾
* 判断新节点的前一个节点的状态,更新,前一个节点,因为在入队的过程中每个节点的状态是动的
* 最后,阻塞当前线程
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 中断状态传播
// 实时或者将来阻塞,抛中断异常
selfInterrupt();
} /**
* 当有新节点入队时,循环的把新节点关联到一个有效节点的后面
* 然后,阻塞这个节点的线程(当前线程)
*/
private boolean acquireQueued(Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (; ; ) {
final Node p = node.predecessor();
// 新节点的前个节点是头结点,如果头结点的线程释放,新节点接可以直接执行
// 所有不要着急阻塞,在判断一次,头结点释放没有,如果头结点释放,新节点不阻塞,把新节点设为头结点
// 当新节点没有排队直接运行了,之后要将节点标记为无效 cancelAcquire
if (p == head && tryAcquire(arg)) {
// 想了很久这段代码发生的情况
// 这段代码发生的情况
// 1.node在入队列时,有不同的线程在获得了锁,且队列中没有节点
// 2.当执行到这里再次tryAcquire之前,之前释放了锁
// 3.这时hasQueuedPredecessors中的判断,头结点的后一个节点,是新建的这个节点,满足s.thread==Thread.currentThread(不考虑这时有其他线程进入,或者进入无效)
// 满足了tryAcquire返回true的情况
// 将头结点改为新节点
/****
* head tail
* | |
* | |
* ---------- ---------
* nullNode newNode
* --------- ----------
* next=newNode prev=nullNode
* prev=null next=null
* ------- ----------
*
* 改完后
*
* head tail
* | |
* | |
* --------- ---------
* nullNode newNode
* --------- ---------
* next=newNode prev=nullNode
* prev=null next=null
* --------- ----------
* */ setHead(node);
p.next = null;
failed = false;
return interrupted;
}
// 之前的节点不是正在执行线程的节点,调整位置和状态再阻塞
// 在线程解除阻塞后,使者节点失效
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
// 节点解除阻塞后,可能是中断或者超时
// 非unlock的解锁
cancelAcquire(node);
}
} private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
// 那个空的节点会保证终止
while (pred.waitStatus > 0)
// 将节点的prev关联到最近的有效节点
node.prev = pred = pred.prev;
Node predNext = pred.next;
// 任何情况都执行的
node.waitStatus = Node.CANCELLED; // 如果取消的节点是队尾节点,并且将前节点设为队尾节点
if (node == tail && compareAndSetTail(node, pred)) {
// cancel的节点和cancel之前的无效节点会移出队列
compareAndSetNext(pred, predNext, null);
} else {
// 如果不是队尾节点
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
// prev->node->next 改为 prev->next
compareAndSetNext(pred, predNext, next);
} else {
// 判断锁定的状态
// 如果前节点是头结点,或者不是SIGNAL状态并且无法设置为SIGNAL状态
// 总结,取消一个节点是,要保证这个节点能被释放,要不通过前节点通知,在锁锁,对应release
unparkSuccessor(node);
} node.next = node; // help GC
}
} private void unparkSuccessor(Node node) {
// 解锁节点的线程
// 当node时头节点时,是当前获取线程释放的炒作
// 不是偷节点
int ws = node.waitStatus;
if (ws < 0)
// 不用再去通知下个节点了,即将释放node了
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 从队尾向前找到最前有效的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); } private void compareAndSetNext(Node pred, Node predNext, Object o) { } private boolean parkAndCheckInterrupt() {
// 阻塞
LockSupport.park(this);
// 当前前程标记中断
return Thread.interrupted();
} private boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 如果前节点是需要被通知的,前节点正在被阻塞,阻塞当先线程
if (ws == Node.SIGNAL)
return true;
// 如果前节点是无效的,找到最近的一个有效节点,并关联,返回,在外部调用方法中会再次调用这个方法
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 这是个切断调用链的过程
pred.next = node;
} else {
// 更新前节点的状态,释放时通知新节点
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
} /**
* 创建节点
* 节点入队
*
* @return 新节点
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 之前有节点在队列中
if (pred != null) {
node.prev = pred;
// 直接修改队尾,不成功要进入接下类的循环,循环中也有类型的判断,这里添加会减少一些逻辑(这样说可能是理解的有偏差)
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
} /**
* 节点入队
* 循环,直到把新节点放到队尾,在多线程中这个过程是不确定的
*/
private Node enq(Node node) {
for (; ; ) {
Node t = tail;
// Must initialize
// 队尾没值,新节点是第一个入队的节点,创建一个空的节点,头尾都指向这个空节点
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
} /**
* 字面理解,是否有已经排队的线程
* 实际意义,有重入锁的情况,在这里要考虑到
* 没有节点在排队的情况,头结点与未节点是相同的
* 判断重入,当前线程是头结点的线程.
*/
protected boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
//为什么是头结点的线程,而不是exclusiveOwnerThread,因为只有在
// 当前队列里没有值得时候才回设置独占线程,如果是通过节点释放的线
// 程还会和节点绑定,不会映射到exclusiveOwnerThread
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
} public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
// 在独占锁的时候,waitStatus只能为0 -1 -2 -3
// 这个里不为0代表头节点是空节点
// 空节点不需要释放
// 头节点是释放锁的时候,最先被考虑的
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
} protected abstract boolean tryRelease(int arg); public void setHead(Node head) {
this.head = head;
} private boolean compareAndSetHead(Node node) {
return (true || false);
} private boolean compareAndSetTail(Node pred, Node node) {
return (true || false);
} protected void selfInterrupt() {
Thread.currentThread().interrupt();
} /**
* CAS更新队列状态,CAS的问题在其他的机会介绍
*/
boolean compareAndSetState(int o, int n) {
return (false || true);
} /**
* 独占线程标记改为指定线程
*/
void setExclusiveOwnerThread(Thread t) {
exclusiveOwnerThread = t;
} /**
* 返回独占线程
*/
Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
} // 修改节点的状态
private boolean compareAndSetWaitStatus(Node pred, int ws, int signal) {
return (true || false);
} static class Node { public int waitStatus; Node() {
} /**
* @param thread
* @param mode SHARED or EXCLUSIVE
*/
Node(Thread thread, Node mode) {
this.thread = Thread.currentThread();
this.mode = mode;
} // 共享模式标记
static final Node SHARED = new Node();
// 独占模式标记
static final Node EXCLUSIVE = null; // 节点被取消,因为超时或者中断
static final int CANCELLED = 1;
// next被阻塞,当节点释放时,notice next
static final int SIGNAL = -1;
// 在条件队列中,等待某个条件被阻塞
static final int CONDITION = -2;
// 节点在共享模式下,可以传播锁
static final int PROPAGATE = -3; volatile Node next;
volatile Node prev;
Node mode; public Thread thread; public Node predecessor() {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
} } /**
* 这是一个独占锁的实现,从ReentrantLock中粘贴出来的部分代码
*/
class SYC extends AQS { public void lock() {
acquire(1);
} public void unlock() {
release(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;
} 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的源码分析(独占、非中断、不超时部分)的更多相关文章

  1. (转)ReentrantLock实现原理及源码分析

    背景:ReetrantLock底层是基于AQS实现的(CAS+CHL),有公平和非公平两种区别. 这种底层机制,很有必要通过跟踪源码来进行分析. 参考 ReentrantLock实现原理及源码分析 源 ...

  2. ReentrantLock 的公平锁源码分析

    ReentrantLock 源码分析   以公平锁源码解析为例: 1:数据结构: 维护Sync 对象的引用:   private final Sync sync; Sync对象继承 AQS,  Syn ...

  3. 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)

    doCreateBean方法 上文[Spring源码分析]非懒加载的单例Bean初始化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下 ...

  4. 【Spring源码分析】非懒加载的单例Bean初始化前后的一些操作

    前言 之前两篇文章[Spring源码分析]非懒加载的单例Bean初始化过程(上篇)和[Spring源码分析]非懒加载的单例Bean初始化过程(下篇)比较详细地分析了非懒加载的单例Bean的初始化过程, ...

  5. Spring源码分析:非懒加载的单例Bean初始化前后的一些操作

    之前两篇文章Spring源码分析:非懒加载的单例Bean初始化过程(上)和Spring源码分析:非懒加载的单例Bean初始化过程(下)比较详细地分析了非懒加载的单例Bean的初始化过程,整个流程始于A ...

  6. Spring源码分析:非懒加载的单例Bean初始化过程(下)

    上文Spring源码分析:非懒加载的单例Bean初始化过程(上),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下AbstractAutowireC ...

  7. ReentrantLock实现原理及源码分析

    ReentrantLock是Java并发包中提供的一个可重入的互斥锁.ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性.只不过相比原生的Sync ...

  8. 【Spring源码分析】非懒加载的单例Bean初始化过程(上篇)

    代码入口 上文[Spring源码分析]Bean加载流程概览,比较详细地分析了Spring上下文加载的代码入口,并且在AbstractApplicationContext的refresh方法中,点出了f ...

  9. Spring源码分析:非懒加载的单例Bean初始化过程(上)

    上文[Spring源码分析]Bean加载流程概览,比较详细地分析了Spring上下文加载的代码入口,并且在AbstractApplicationContext的refresh方法中,点出了finish ...

随机推荐

  1. InventSumDelta表的作用

    https://groups.google.com/forum/#!topic/microsoft.public.axapta.programming/rRfbJo9M0dk The purpose ...

  2. 推荐10款免费的在线UI测试工具

    发布网站之前至关重要的一步是网站测试.网站测试要求我们全面地运行网站并通过所有基本测试,如响应式设计测试.安全测试.易用性测试.跨浏览器兼容性.网站速度测试等. 网站测试对SEO.搜索引擎排名.转换率 ...

  3. stopping NetworkManager daemon failed

    1 初次安装NetworkManager时发现,无法将这个服务关闭 2 上网找了一圈,也没找到原因 3 重启服务器后就能正常关闭了 4 将该服务删除重装也能正常关闭 5 下回重装系统时再观察一下

  4. Linux小知识积累

    1.Linux图形界面和字符命令行界面的切换 从图形界面切换到字符界面,使用快捷键 Ctrl+Alt+F1 从字符界面切换到图形界面,使用快捷键 Ctrl+Alt+F7 2.解压文件 tar -xzv ...

  5. Check if a configuration profile is installed on iOS

    Configuration profiles can be downloaded to an iOS device through Safari to configure the device in ...

  6. 一个面试题的解答-----从500(Id不连续)道试题库里随机抽取20道题!

    做一个考试系统的项目,现在从试题库里面随机抽取20道题 比如我题库有500道题(ID不连续).题目出现了,如何解决呢,随机抽取! 1,我们先把500道题的id存进一个长度为500的数组. 2,实现代码 ...

  7. php 图片验证码生成 前后台验证

    自己从前一段时间做了个php小项目,关于生成图片验证码生成和后台的验证,把自己用到的东西总结一下,希望大家在用到相关问题的时候可以有一定的参考性. 首先,php验证码生成. 代码如下: 1.生成图像代 ...

  8. Python中的下划线(译文)

    原文地址这篇文章讨论Python中下划线_的使用.跟Python中很多用法类似,下划线_的不同用法绝大部分(不全是)都是一种惯例约定. 单个下划线(_) 主要有三种情况: 1. 解释器中 _符号是指交 ...

  9. c++内存对齐

    内存对齐原则: 1.数据成员对齐规则:struct, union的数据成员,第一个数据成员放在offset为0的地方,之后的数据成员的存储起始位置都是放在该数据成员大小的整数倍位置.如在32bit的机 ...

  10. C# 多线程限制方法调用(monitor)

    多线程执行方法 改方法没有执行完时 别的方法不能调用次方法.用循环执行一个方法可以需要一分钟 在这一分钟只内任何 成员都不能再调用该方法. class MonitorSample { ; //生产者和 ...