abstractqueuedsynchronizer详解(一)——分析reentrantlock源码_第1页
abstractqueuedsynchronizer详解(一)——分析reentrantlock源码_第2页
abstractqueuedsynchronizer详解(一)——分析reentrantlock源码_第3页
abstractqueuedsynchronizer详解(一)——分析reentrantlock源码_第4页
abstractqueuedsynchronizer详解(一)——分析reentrantlock源码_第5页
已阅读5页,还剩10页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

AbstractQueuedSynchronizer 详解(一) 分析 ReentrantLock 源码 官方文档如是说 AQS 提供了一个框架,一个 FIFO 的等待队列和一个代表状态的 int 值。子类需要定义改变 这个状态的 protected 方法、定义什么状态表示获取到状态以及释放状态。该类中其中方法 提供所有入队和阻塞机制。子类可以保存其他状态,但是需要使用 getState、setState 和 compareAndSetState 方法来原子性地读取更新状态值。 AQS 用于一群线程为了得到某个资源,但是同一时刻,只能有部分线程可以使用该资源, 对于其他线程,会使用队列将其他线程入队,而一旦有一个线程使用完了资源,那么队列 中的某个线程就会获得该资源的使用。而 AQS 中未对该资源做出明确说明,只是通过一个 int 的状态值表示资源的获取与释放。比如在 ReentrantLock 中,该资源某一线程持有的锁 的个数,当某一线程获得了这把锁,就把状态值置为 1,释放则将状态值减 1。那么就会得 出状态 0 表示线程可以使用这把锁,而状态大于 0 则表示其他线程不可以使用这把锁。由 于 ReentrantLock 是可以重入的,所以这儿状态值是可以大于 1,代表拥有该锁的线程又再 一次获得该锁,这些留在下面再讲。 一个状态 /* * The synchronization state. */ private volatile int state; /* * Returns the current value of synchronization state. * This operation has memory semantics of a code volatile read. * return current state value */ protected final int getState() return state; /* * Sets the value of synchronization state. * This operation has memory semantics of a code volatile write. * param newState the new state value */ protected final void setState(int newState) state = newState; /* * Atomically sets synchronization state to the given updated * value if the current state value equals the expected value. * This operation has memory semantics of a code volatile read * and write. * * param expect the expected value * param update the new value * return code true if successful. False return indicates that the actual * value was not equal to the expected value. */ protected final boolean compareAndSetState(int expect, int update) / See below for intrinsics up to support this return pareAndSwapInt(this, stateOffset, expect, update); AQS 中表示状态的值是一个 valotile 类型的,实现了内存可见性,一旦更新,那么别的线 程就可以立即看到了。而对于更新,使用了 Unsafe 类的 CAS 机制实现原子性。所以关于 state 的更新以及读取就不需要子类关注了,只需要知道调用这几个方法即可。 队列 AQS 支持两种模式,一种是独占式,另一种是共享式。 当使用独占式时,当一个线程得到了资源,那么其他尝试获取资源的线程将不会成功。 当使用共享式时,当一个线程得到了资源,那么其他尝试获取资源的线程可能(也有可能 不)会成功。 不同模式的等待线程使用相同的队列,都在同一个队列中入队。所以队列的每一个节点需 要区分是共享模式还是独占模式。 通常,子类只会支持一种模式,但是 ReadWriteLock 类使用了两种模式。只支持一种模式 的子类不需要重写另一种模式的方法。 下面是队列中节点的定义: static final class Node /表示共享模式 static final Node SHARED = new Node(); /表示独占模式 static final Node EXCLUSIVE = null; /表明线程已被取消 static final int CANCELLED = 1; /后继节点需要被唤醒 static final int SIGNAL = -1; /表明线程在等待某个条件的发生 static final int CONDITION = -2; /表明下一次 acquireShared 时需要无条件地传播 static final int PROPAGATE = -3; /该线程的状态 volatile int waitStatus; /前驱节点 volatile Node prev; /后继节点 volatile Node next; /当前线程 volatile Thread thread; /下一个等待条件的节点或共享节点 Node nextWaiter; /* * 返回该节点是否是共享的 */ final boolean isShared() return nextWaiter = SHARED; /返回该节点的前驱节点 final Node predecessor() throws NullPointerException Node p = prev; if (p = null) throw new NullPointerException(); else return p; Node() / Used to establish initial head or SHARED marker Node(Thread thread, Node mode) / Used by addWaiter this.nextWaiter = mode; this.thread = thread; Node(Thread thread, int waitStatus) / Used by Condition this.waitStatus = waitStatus; this.thread = thread; 从上面可以看到 AQS 的队列是一个双向链表结构的队列。而在 AQS 中也保存了一头一尾 两个变量,这样入队和出队都很方便。 /* * Head of the wait queue, lazily initialized. Except for * initialization, it is modified only via method setHead. Note: * If head exists, its waitStatus is guaranteed not to be * CANCELLED. */ private transient volatile Node head; /* * Tail of the wait queue, lazily initialized. Modified only via * method enq to add new wait node. */ private transient volatile Node tail; 独占模式的例子 ReentrantLock 是可重入的锁,其内部使用的就是独占模式的 AQS,那么下面就以 ReentrantLock 为例,说明一下 ReentrantLock 的实现原理。 ReentrantLock 的使用 ReentrantLock 的使用有一定的格式,如下: class X * private final ReentrantLock lock = new ReentrantLock(); * / . * * public void m() * lock.lock(); / block until condition holds * try * / . method body * finally * lock.unlock() * * * lock 方法用于尝试获取锁,一旦得到锁后就可以进行操作了,否则线程阻塞;最后处理完 事情后调用 unlock 释放锁。下面就从这两个方法看起。 AQS 的状态在 ReentrantLock 代表什么? 因为 ReentrantLock 是可重入的,那么 AQS 的状态在 ReentrantLock 代表的是锁的持有数。 虽然一次只能有一个线程持有该锁,但是由于可重入的原因,导致持有线程可以获得多把 锁。 lock 方法 首先看 lock 方法,代码如下: public void lock() sync.lock(); 可以看到 lock 方法调用 sync 的 lock 方法,那么这个 sync 是个什么呢? Sync 是 ReentrantLock 的一个内部类,并且在 ReentrantLock 初始化时创建。下面是 Sync 类的定义: abstract static class Sync extends AbstractQueuedSynchronizer private static final long serialVersionUID = -5179523762034025860L; abstract void lock(); /* * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ 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) /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ 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 dont park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); return false; 从代码中可以看出,只有当前一个节点为 SIGNAL 状态时,才可以将自身挂起,而挂 起的 parkAndCheckInterrput()如下: private final boolean parkAndCheckInterrupt() LockSupport.park(this); /挂起线程 return Terrupted(); 上面是没有获取到的情况,需要挂起线程,那么如果获得了资源,那么会调用 setHead 方法,下面是 setHead 方法 private void setHead(Node node) /设置成头节点 head = node; /置空 node.thread = null; node.prev = null; 从上面的代码可以看到,setHead 方法就是将节点设置为队列中的空节点,这里将线程 置为 null 了。那么节点之前关联的线程呢? 已经在 tryAcquire 方法中设置了拥有 AQS 的线程了。 FairSync 的 lock 方法 到上面为止,就分析完了 NonfairSync 的 lock 方法,下面再看下 FairSync 的 lock 方法, 比较一下区别: final void lock() acquire(1); protected final boolean tryAcquire(int acquires) /获得当前线程 final Thread current = Thread.currentThread(); /状态数 int c = getState(); /没有线程拥有锁 if (c = 0) /如果队列为空并且可以成功更新状态 if (!hasQueuedPredecessors() return true; /如果本线程与持有锁的线程相同 else if (current = getExclusiveOwnerThread() int nextc = c + acquires; if (nextc 0) s = null; /从队列后出发,寻找最后一个不为 null 且状态为负的节点 for (Node t = tail; t != null t = t.prev) if (t.waitStatus = 0) s = t; /如果节点不为 null,唤醒该节点 if (s != null) LockSupport.unpark(s.thread); 之前我们知道了队列中的第一个节点是个空节点,这个操作就是遍历其后节点找到第 一个需要唤醒的节点。当一个节点被唤醒后,其 lock 方法就会被释放,而关键的是 acquireQueued 方法中死循环会继续执行,下面再看

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论