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 ...
随机推荐
- [spark案例学习] 单词计数
数据准备 数据下载:<莎士比亚全集> 我们先来看看原始数据:首先将数据加载到RDD,然后显示数据框的前15行. shakespeareDF = sqlContext.read.text(f ...
- [UCSD白板题] Least Common Multiple
Problem Introduction The least common multiple of two positive integers \(a\) and \(b\) is the least ...
- [UCSD白板题] The Last Digit of a Large Fibonacci Number
Problem Introduction The Fibonacci numbers are defined as follows: \(F_0=0\), \(F_1=1\),and \(F_i=F_ ...
- jquery 模拟 alert 手机,pc,平板 3合一
$.kw = { title : "System information", //默认标题 可修改 speed : 400, //默认速度 可修改 buttonName : &qu ...
- oracle迁移mysql数据库注意
oracle转mysql修改: . substr() substr( string , 0, 10) 这里测试 必须从 第一位获取 既是 substr(string , 1 , 10)2. to_ch ...
- iOS 笔记
1. 使用断言NSAssert()调试程序错误 NSAssert()只是一个宏,用于开发阶段调试程序中的Bug,通过为NSAssert()传递条件表达式来断定是否属于Bug,满足条件返回真值,程序继续 ...
- [转载]iOS9 使用CoreLocation
在iOS8之前,只要 #import <CoreLocation/CoreLocation.h>引入CoreLocation.framework. @property (nonatomic ...
- ajax500错误
昨天做一个需求,原先使用form提交的东西,领导说要改成使用ajax提交.嗯,听起来好像很简单很简单哦,可惜我已经很少敲代码了.擦,这工作让人槽点无数.果断写代码. var fm=document.g ...
- linux系统用户以及用户组管理
本系列的博客来自于:http://www.92csz.com/study/linux/ 在此,感谢原作者提供的入门知识 这个系列的博客的目的在于将比较常用的liunx命令从作者的文章中摘录下来,供自己 ...
- c# 列举所有窗口和子窗口
private delegate bool WNDENUMPROC(IntPtr hWnd, int lParam); [DllImport("user32.dll", Exact ...