【Java8源码分析】locks包-AbstractQueuedSynchronizer同步器
来源:互联网 发布:centos 创建文件 编辑:程序博客网 时间:2024/05/24 07:05
转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72844011
1 前言
目前在Java中存在两种锁机制:synchronized和Lock,Lock接口及其实现类是JDK5增加的内容。数据同步需要依赖锁,那锁的同步又依赖谁?synchronized给出的答案是在软件层面依赖JVM,而Lock给出的方案是在硬件层面依赖特殊的CPU指令。
在java中的locks包中的ReentrantLock、ReentrantReadWriteLock,还有同步包中的Semaphore、CountDownLatch类均是借助AQS同步器实现。
AQS的功能可以分为两类:独占锁和共享锁。它的所有子类中,要么实现并使用了它独占锁的API,要么使用了共享锁的API,而不会同时使用两套API,即便是它最有名的子类ReentrantReadWriteLock,也是通过两个内部类:读锁和写锁,分别实现的两套API来实现的
2 概要
AQS类中,有两件重要的事情,即获取锁和释放锁。AQS类将其分为独占模式和共享模式
- 独占模式:其他线程试图获取该锁将无法取得成功
- 共享模式下:多个线程获取某个锁可能(但不是一定)会获得成功
// 独占模式,获取锁的入口public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}// 共享模式,释放锁的入口public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false;}// 共享模式,获取锁的入口public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg);}// 共享模式,释放锁的入口public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false;}// 以下四个方法需要子类实现protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException();}protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException();}protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException();}protected boolean tryRelease(int arg) { throw new UnsupportedOperationException();}
上面是AQS类的几个重要方法,其中tryAcquire(int)、tryRelease(int)、tryAcquireShared(int)、tryReleaseShared(int)四个方法是需要子类实现的。
3 存储结构
在AQS中,被阻塞的线程被包装成一个Node结构,而且阻塞的线程(Node)被存储在一个双向链表当中,如下图所示
+------+ prev +-------+ prev +------+ | Node | <---- | Node | <---- | Node | head |thread| |thread | |thread| tail | ws | ----> | ws | ----> | ws | +------+ next +-------+ +------+static final class Node { // 模式,分为共享与独占 // 共享模式 static final Node SHARED = new Node(); // 独占模式 static final Node EXCLUSIVE = null; // 结点状态 // CANCELLED,值为1,表示当前的线程被取消 // SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark // CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中 // PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行 // 值为0,表示当前节点在sync队列中,等待着获取锁 static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; // 结点状态 volatile int waitStatus; // 前驱结点 volatile Node prev; // 后继结点 volatile Node next; // 结点所对应的线程 volatile Thread thread; // 下一个等待者 Node nextWaiter; }
4 源码分析
属性域
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable { // 头结点 private transient volatile Node head; // 尾结点 private transient volatile Node tail; // 状态,0代表没有线程获取该锁,>0代表线程重入的次数 private volatile int state;}
AQS获取锁的比较复杂,下面以三个线程A、B为例说明
Step 1
线程A通过acquire
获得锁,并且线程A执行,此时状态如图
Step 2
线程B通过acquire
获得锁,过程如代码注释,中间状态如下图所示。
public final void acquire(int arg) { // tryAcquire返回false if (!tryAcquire(arg) && // 先调用addWaiter // 再调用acquireQueued acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt();}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; } } // 尾结点为空(即还没有被初始化过),或者是compareAndSetTail操作失败,则入队列 enq(node); return node; }// 加入队列private Node enq(final Node node) { for (;;) { Node t = tail; // 尾结点为空,即还没被初始化 if (t == null) { // 头结点为空,并设置头结点为新生成的结点 if (compareAndSetHead(new Node())) tail = head; } else { // 尾结点不为空,即已经被初始化过 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
Step 3
由enq函数的代码可知,其实enq是自旋的,上面展示了enq执行第一次循环时候的状态,接下来循环第二次,并返回给acquireQueued,下面来看看acquireQueued。
//判断在等待队列中的线程是否中断final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // 自旋,每次循环尝试获得锁,如果尝试不成功后会挂起线程 // 等待release唤起 for (;;) { final Node p = node.predecessor(); // 如果前驱节点是head节点,则尝试再获得锁,因为锁可能已经释放 // 如果获取锁成功 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); }}private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) // 前驱节点已经设置为release时通知它,故可以“停靠”在此节点后面 return true; if (ws > 0) { // 前驱节点已经cancel,这个时候我们会除掉这个节点 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 如果都不是,通过CAS操作将这个前驱节点设置成SIGHNAL compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false;}
注意:此时head节点的waitStatus已经被更新为-1,但NodeB在逻辑上并为成功park在head节点后。
Step 4
acquireQueued
是自旋的,Step 3中已经把前驱节点的状态设置为-1,即设置为通知状态,此时自旋重复上述Step 3,加入tryAcquire
仍为false,即获取不了锁,此时调用shouldParkAfterFailedAcquire
返回true,该NodeB在逻辑上成功park在head节点后,然后调用parkAndCheckInterrupt
挂起
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted();}
Step 5
等待线程A执行完成了,调用release
函数释放锁,实际上调用tryRelease
,是AQS的state
减1,如果返回true,则成功释放。接下来调用unparkSuccessor
唤起等待线程
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false;}private void unparkSuccessor(Node node) { // 把node的状态变为0 int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 查找下一个状态-1的节点,即需要唤起的node 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; } // 调用unpark唤起 if (s != null) LockSupport.unpark(s.thread);}
5 总结
(1)线程被包装成Node,被保存在双向链表,同时有一个head和tail辅助指针。同时Node中还有waitStatus状态变量,只有一个Node的状态是-1时,才能停靠一个后续Node。
(2)head指针指向一个Node时,会将这个Node的状态设置为0,可以理解为当前获得锁的Node。
参考
http://blog.csdn.net/pfnie/article/details/53191892
http://www.jianshu.com/p/d8eeb31bee5c
转载请注明出处:http://blog.csdn.net/linxdcn/article/details/72844011
- 【Java8源码分析】locks包-AbstractQueuedSynchronizer同步器
- 【Java8源码分析】locks包-ReentrantLock
- 【Java8源码分析】locks包-ReentrantReadWriteLock
- java.util.concurrent.locks.AbstractQueuedSynchronizer队列同步器源码解析
- JUC源码解析(6)-locks-AbstractQueuedSynchronizer
- Java 并发包中AbstractQueuedSynchronizer 实现的同步器
- AQS(AbstractQueuedSynchronizer)源码分析
- AbstractQueuedSynchronizer 源码分析
- AbstractQueuedSynchronizer 源码分析
- AbstractQueuedSynchronizer源码分析
- 【Java8源码分析】并发包-AtomicInteger
- 【Java8源码分析】并发包-CopyOnWriteArrayList
- 【Java8源码分析】并发包-Semaphore
- 【Java8源码分析】并发包-CountDownLatch
- 【Java8源码分析】并发包-CyclicBarrier
- 【Java8源码分析】NIO包-FileChannel
- 【Java8源码分析】NIO包-Selector选择器
- JUC - AbstractQueuedSynchronizer(AQS) 源码分析
- java并发编程(五)--Java中的锁(读写锁ReentrantReadWriteLock)
- JAVA内存区域与内存溢出相关设置
- linux jenkins 自动化部署工具安装
- Java-快速排序,java.lang.StackOverflowError堆栈溢出异常处理
- centOS下搭建WordPress
- 【Java8源码分析】locks包-AbstractQueuedSynchronizer同步器
- 前沿技术解密——Virtual DOM
- 你真的会用Gson吗?Gson使用指南(一)
- react-native 安卓打包流程
- JavaWeb
- Spring Cloud构建微服务架构(四)分布式配置中心(续)
- 新路程----linux framebuffer显示图片c程序
- Unity3D -- 调用手机端发送邮件功能
- Linux目录结构及详细介绍