Reentrantlock源码剖析--菜鸟一枚,鼓励指正
2016-04-28 17:26
417 查看
Reentrantlock源码剖析(未完待续)
Reentrantlock类中大约共有21个方法,三个内部类(Syn,FairSyn,NonfairSync),实现了接口Serializable,主要的加锁解锁功能,是通过三个内部类完成的。在所有的方法中涉及到加锁的方法有三个,lock(),tryLock(),tryLock(long,TimeUnit ),涉及到解锁的一个unlock().与Condition有关的共四个方法(后续介绍),其他的函数基本上是Getter函数。加锁
流程图:- lock()
先看一下啊lock的源码,sync是什么?查看声明, private final Sync sync;sync是Sync的一个对象,其中Sync就是前面提到的三个内部类的其中之一,也是另外两个类的父类,Sync继承了一个听说很重要的类AbstractQueuedSynchronizer,没有具体深入,待后续跟进。
public void lock() { sync.lock(); }
lock()在Sync中是一个抽象函数,具体的定义延迟到了子类中,这样子类可以更加灵活的去实现它,这里用到了一个设计模式,模版设计模式父类声明子类实现父类调用。这里lock()函数实现的子类就是ReentrantLock中的另外两个内部类FairSyn,NonfairSync,以NonfairSync内部类进行剖析。
final void lock() { //CAS操作,将state设为1,state是用来表示有无锁竞争 if (compareAndSetState(0, 1)) //获取锁成功,将当前的锁的所有现场设为当前线程 setExclusiveOwnerThread(Thread.currentThread()); else acquire(1);//获取失败调用acquire试图再次请求或者阻塞自己 }
acquire()是来自AbstractQueuedSynchronizer类具体实现如下
public final void acquire(int arg) { //再一次尝试获取锁若成功则返回,其中tryAcquire的实现实在子类中,若获取失败则调用acquireQueued()将加入到队列中的当前线程阻塞 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
下面是非公平锁的tryAcquire()具体实现,间接调用了nonfairTryAcquire(),首先获取state,判断当前有无线程占有该锁,c==0无竞争,通过CAS操作修改当前的state,如果c!=0在判断当前锁占有线程是不是当前线程,若是不用在获取锁,直接跟新state,此时不许用cas操作,线程安全,偏向锁。如果全部失败返回false,调用acquireQueued()将加入到队列中的当前线程阻塞。
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
下面是addWaiter()的函数实现,Node类是AbstractQueuedSynchronizer的一个内部类,个人理解是用来维护整个阻塞队列的。首先通过当前线程构造一个Node,获取链表的末尾节点,判断末尾节点是否存在,若pred!=null,则CAS操作直接将node插入到末尾节点后面,这里的链表是一个双向链表。若末尾节点为空,则调用enq()函数,将当前节点加入到队列中,具体看enq函数的源码。
private Node addWaiter(Node mode) { //mode注意文章最后面的介绍 Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure 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(final Node node) { for (;;) {//无限循环,确保当前节点入队列 Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
将当前线程入队列之后,因为获取不要锁,则需要将当前线程阻塞,acquireQueued()源码如下,在试图阻塞线程之前,每次都要重新获取一下锁,万一成功了呢,看似会死循环,我们来看一下shouldParkAfterFailedAcquire(p, node) 和parkAndCheckInterrupt()都做了什么
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) {//无限循环,确保当前线程要么获得锁,要么被阻塞 final Node p = node.predecessor(); //如果当前节点的前一个节点是头结点,则尝试再一次获取锁 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC 设置为空帮助GC回收 failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
shouldParkAfterFailedAcquire()(ps:这里我把原来的英文注释删掉了,如果你英文好,就不要看完我在这瞎bb了,直接看原注释就行,比我说的好。)首先查看了前一个node的节点状态:
规则1:如果前继的节点状态为SIGNAL,表明当前节点需要unpark,则返回成功,此时acquireQueued方法的第12行(parkAndCheckInterrupt)将导致线程阻塞
规则2:如果前继节点状态为CANCELLED(ws>0),说明前置节点已经被放弃,则回溯到一个非取消的前继节点,返回false,acquireQueued方法的无限循环将递归调用该方法,直至规则1返回true,导致线程阻塞
规则3:如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,返回false后进入acquireQueued的无限循环,与规则2同
[code]private static 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
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序
- 二叉查找树