锁就是一种状态,比如互斥锁:同一时间只能有一个线程拥有,可以使用一个整型值来标志当前的状态

  • 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的更多相关文章

  1. Java 线程池框架核心代码分析--转

    原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...

  2. Java线程问题分析定位

    Java线程问题分析定位 分析步骤: 1.使用top命令查看系统资源占用情况,发现Java进程占用大量CPU资源,PID为11572: 2.显示进程详细列表命令:ps -mp 11572 -o THR ...

  3. 怎样分析java线程堆栈日志

    注: 该文章的原文是由 Tae Jin Gu 编写,原文地址为 How to Analyze Java Thread Dumps 当有障碍,或者是一个基于 JAVA 的 WEB 应用运行的比预期慢的时 ...

  4. Java 线程池框架核心代码分析

    前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和资源消耗都是很高的.线程池应运而生,成为我们管理线程的利器.Java 通过Executor接口,提供了一种标准的方法将任务的提交过 ...

  5. Java线程同步之一--AQS

    Java线程同步之一--AQS 线程同步是指两个并发执行的线程在同一时间不同时执行某一部分的程序.同步问题在生活中也很常见,就比如在麦当劳点餐,假设只有一个服务员能够提供点餐服务.每个服务员在同一时刻 ...

  6. Java线程池使用和分析(二) - execute()原理

    相关文章目录: Java线程池使用和分析(一) Java线程池使用和分析(二) - execute()原理 execute()是 java.util.concurrent.Executor接口中唯一的 ...

  7. Java 线程同步组件 CountDownLatch 与 CyclicBarrier 原理分析

    1.简介 在分析完AbstractQueuedSynchronizer(以下简称 AQS)和ReentrantLock的原理后,本文将分析 java.util.concurrent 包下的两个线程同步 ...

  8. 并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)

    史上最清晰的线程池源码分析 鼎鼎大名的线程池.不需要多说!!!!! 这篇博客深入分析 Java 中线程池的实现. 总览 下图是 java 线程池几个相关类的继承结构:    先简单说说这个继承结构,E ...

  9. Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理

    相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...

随机推荐

  1. 《转》python线程池

    线程池的概念是什么? 在IBM文档库中这样的一段描写:“在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是 如此,虚拟机将试图跟踪每一个对象 ...

  2. javascript练习-扑克牌

    下面用枚举类型来实现一副扑克牌的类: //定义一个玩牌的类 function Card(suit,rank){ function inherit(p){ if(p==null) throw TypeE ...

  3. Windows XP和Word 2007不能正常使用VSTO插件

    今天帮助同事解决了一个小问题,就是在WindowsXP上,为Word2007开发的插件不能正常显示. 通过搜索关键词 WindowsXp Word 2007 VSTO找到了两个解决方案. http:/ ...

  4. disconf使用

    1.创建app,确定version 2.创建配置文件redis.config 3.选择app下env环境,上传redis.config到disconf 4.创建disconf.properties到c ...

  5. 网页播放器(jsp、js)

    jsp对控件显示 <%@ page language="java" import="java.util.*" pageEncoding="UTF ...

  6. 有用的php函数

    filter系列函数 filter_input   通过名称获取特定的外部变量,并且可以通过过滤器处理它 filter_input(INPUT_GET, 'a', FILTER_SANITIZE_NU ...

  7. 在浏览器的JavaScript里new Date().toUTCString()后,传递给C# DateTime().TryParse()会发生什么?

    Format 1. Sun, 09 Oct 2016 13:24:35 GMT Format 2. Sun, 9 Oct 2016 13:36:09 UTC Format 1 是在IE里面产生的(Wi ...

  8. highcharts 柱状图 折线图 混合 双纵轴显示

    $(function () { $('#container').highcharts({ chart: { zoomType: 'xy' }, title: { text: '' }, colors: ...

  9. 错误 "sgen.exe" exited with code 1.解决方法(转)

    原文出自 http://blog.sina.com.cn/s/blog_8411d3f401015u1w.html VS中有时候编译项目会出现这样的错误: 错误   "sgen.exe&qu ...

  10. 可在广域网部署运行的QQ高仿版 -- GG叽叽V3.4,增加系统设置、最近联系人、群功能(源码)

    自从上次版本(GG叽叽V3.2,增加离线消息.离线文件功能)发布后,我个人觉得主要的大功能都实现得差不多了,接下来的几个版本将不断优化GG的细节,提高其可用性.这次版本更新的内容主要是为GG增加了系统 ...