Java 线程 — AbstractQueuedSynchronizer
锁
锁就是一种状态,比如互斥锁:同一时间只能有一个线程拥有,可以使用一个整型值来标志当前的状态
- 0:表示没有现成占有锁
- 1:表示锁已经被占用
AbstractQueuedSynchronizer
- 实现Java中锁的基础,提供了一系列模板方法,程序员可以继承该类作为内部类实现自定义同步语义。
- 通过一个整型变量state维护锁的同步状态
- 通过CAS保证同步状态state的修改是原子的
这个抽象类中使用了很多模板方法,在实现锁机制的时候可以调用这些模板方法:

模板方法里面调用了一些尚未实现、留给程序员实现的抽象方法:
独占式同步方法:

共享式同步方法:

独占式同步状态
同时只能有一个线程占有锁,独占式同步过程:
// 获取锁,可以在在lock方法中调用
public final void acquire(int arg) {
// tryAcquire方法需要根据锁的功能自行实现
// 获取锁失败的话执行addWaiter,Node.EXCLUSIVE表明当前node获取的是独占式的锁
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 因为获取锁失败,将当前node加入queue的最后一个节点tail
private Node addWaiter(Node mode) {
// 新建的node是独占模式,不对waitStatus赋值,也就是默认为0
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
// 如果队列不为空,则插入tail
if (pred != null) {
node.prev = pred;
// 因为是独占式的锁,只会有一个线程修改tail,不要循环
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果tail == null表示队列为空,调用enq入队
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 因为在这个时候可能其他线程已经入队,队列可能不再为null
// 如果依然为空,则初始化队列——新建一个node,并设为head=tail=newNode
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 如果其他线程已经入过队,则正常设置tail
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// 入队列之后进入自旋状态
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 不断循环尝试获取锁——自旋
for (;;) {
final Node p = node.predecessor(); // 获取前一个node
// 如果前一个node是head,则尝试获取锁,因为如果前一个是head说明head有可能已经释放锁,当前node可以尝试获取锁
if (p == head && tryAcquire(arg)) {
// 如果获取成功则将当前node设为head
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 判断是否需要park
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 这个方法可以结合释放锁release(释放独占锁)和releaseShared(释放共享锁)来看,因为释放锁的时候会判断是否进行unpark操作
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
// 前面一个节点是signal,表示前面一个节点释放锁的时候(release)一定会unpark,那么当前node可以park(因为一定会被unpark,保证不会被永久阻塞)
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
// 把已经是cancelled的node从队列中移除,不再进行获取锁
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
释放独占锁:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
// 独占锁的node.waitStatus默认是0,因为在初始化的时候不会对waitStatus赋值,如果不等于0,表示在加入队列的时候设置过waitStatus,设置为SIGNAL
if (h != null && h.waitStatus != 0)
// 如果head的waitStatus是SIGNAL表示要唤醒阻塞的线程
unparkSuccessor(h);
return true;
}
return false;
}
共享式同步状态
可以同时有多个线程获得锁
重入锁
同一个线程重复获得锁
读写锁
读锁:共享锁
写锁:独占锁
LockSupport
调用Unsafe的方法,负责阻塞或者唤醒线程。相当于一个线程有一个许可,默认是被占用的,park会占用这个许可,unpark会分配这个许可,只要调用过至少一次unpark,那么再调用一次park线程会返回,不会被永久阻塞:
- 如果先调用park并且后面不会调用unpark,那么线程会被一直阻塞;
- 如果先调用unpark(至少一次),再调用park,那么线程立即返回,不会阻塞
- 如果先调用park(至少一次),再调用unpark,那么线程在调用unpark后返回,不会继续阻塞
所以unpark会给当前线程分配一个许可,如果之前调用过一次park或者后面调用一次park,那么线程不再继续阻塞,即:
- 一个线程只有一个许可
- unpark分配许可
- park占用许可
- 只有调用unpark的次数 >= 调用park的次数,线程才不会被永久阻塞
Condition
任意一个Java对象,都拥有一组Monitor方法(定义在Object上),包括:wait,notify等,与synchronized配合实现等待/通知模式
Lock和Condition结合也可以实现等待/通知模式
Java 线程 — AbstractQueuedSynchronizer的更多相关文章
- Java 线程池框架核心代码分析--转
原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...
- Java线程问题分析定位
Java线程问题分析定位 分析步骤: 1.使用top命令查看系统资源占用情况,发现Java进程占用大量CPU资源,PID为11572: 2.显示进程详细列表命令:ps -mp 11572 -o THR ...
- 怎样分析java线程堆栈日志
注: 该文章的原文是由 Tae Jin Gu 编写,原文地址为 How to Analyze Java Thread Dumps 当有障碍,或者是一个基于 JAVA 的 WEB 应用运行的比预期慢的时 ...
- Java 线程池框架核心代码分析
前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和资源消耗都是很高的.线程池应运而生,成为我们管理线程的利器.Java 通过Executor接口,提供了一种标准的方法将任务的提交过 ...
- Java线程同步之一--AQS
Java线程同步之一--AQS 线程同步是指两个并发执行的线程在同一时间不同时执行某一部分的程序.同步问题在生活中也很常见,就比如在麦当劳点餐,假设只有一个服务员能够提供点餐服务.每个服务员在同一时刻 ...
- Java线程池使用和分析(二) - execute()原理
相关文章目录: Java线程池使用和分析(一) Java线程池使用和分析(二) - execute()原理 execute()是 java.util.concurrent.Executor接口中唯一的 ...
- Java 线程同步组件 CountDownLatch 与 CyclicBarrier 原理分析
1.简介 在分析完AbstractQueuedSynchronizer(以下简称 AQS)和ReentrantLock的原理后,本文将分析 java.util.concurrent 包下的两个线程同步 ...
- 并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)
史上最清晰的线程池源码分析 鼎鼎大名的线程池.不需要多说!!!!! 这篇博客深入分析 Java 中线程池的实现. 总览 下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,E ...
- Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理
相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...
随机推荐
- 解决Chrome重启后插件被禁用的问题
下载组策略模版,添加白名单 http://pan.baidu.com/s/1o88kcZo 打开组策略 win+r 打开运行,输入 gpedit.msc 添加模版 右键 ->管理模版 -&g ...
- node的事件模块应用(译)
第一次接触Node.js时,就觉得他只不过是用javascript实现的服务端.但实际上他提供了许多浏览器端不具备的方法,比如EventEmitter类.我们在本文中来学习如何使用EventEmitt ...
- java中打印变量地址
在java中打印变量的地址 这个代码是在startoverflow上看到的,跟大家分享一下. import sun.misc.Unsafe; import java.lang.reflect.Fiel ...
- wince mobile环境下播放WAV声音
[DllImport("coredll", EntryPoint = "PlaySound")] public static extern i ...
- linux琐碎命令学习
kill -l会把linux的信号都列出来.1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP6) SIGABRT 7) SIGBUS 8) SIG ...
- 三星(SAMSUNG)910S3L-K04 安装win7的BIOS设置
三星(SAMSUNG)910S3L-K04 开机后连续点击F2进入BIOS,再进入BOOT.将SECURE BOOT CONTROL点成disabled,将OS MODE SELECTION选为uef ...
- java-集合类
框架图 集合类 面向对象语言对事物的体现都是以对象的形式,所以为了方便对多个对象的操作,就对对象进行存储,集合就是存储对象最常用的一种方式.数组和集合类同是容器,有何不同?数组存储同一类型的基本数据类 ...
- js基础知识:表达式
一.什么是表达式?我理解的"表达式":程序执行到1个"表达式"时,会返回1个值到这个"表达式"所在的位置. var a = 10 , b = ...
- js jq 获取网页元素宽度
Javascript: IE中:document.body.clientWidth ==> BODY对象宽度document.body.clientHeight ==> BODY对象高度d ...
- 简单的OkHttp使用介绍
Android系统提供了两种HTTP通信类,HttpURLConnection和HttpClient.关于HttpURLConnection和HttpClient的选择>>官方博客尽管Go ...