PHP+Ajax从入门到精通全套教程
AQS 结构
先来看看 AQS 有哪些属性,搞清楚这些基本就知道 AQS 是什么套路了!
// 头结点,你直接把它当做 当前持有锁的线程 private transient volatile Node head; // 阻塞的尾节点,每个新的节点进来,都插入到最后,也就形成了一个隐视的链表 private transient volatile Node tail; // 这个是最重要的,不过也是最简单的,代表当前锁的状态,0代表没有被占用,大于0代表有线程持有当前锁 // 之所以说大于0,而不是等于1,是因为锁可以重入嘛,每次重入都加上1 private volatile int state; // 代表当前持有独占锁的线程,举个最重要的使用例子,因为锁可以重入 // reentrantLock.lock()可以嵌套调用多次,所以每次用这个来判断当前线程是否已经拥有了锁 // if (currentThread == getExclusiveOwnerThread()) {state++} private transient Thread exclusiveOwnerThread; //继承自AbstractOwnableSynchronizer
AbstractQueuedSynchronizer 的等待队列示意如下所示,注意了,之后分析过程中所说的 queue,也就是阻塞队列不包含 head,因为head表示当前持有锁的线程,并没有在等待获取锁。
等待队列中每个线程被包装成一个 node,数据结构是链表,一起看看源码吧:
static final class Node { /** Marker to indicate a node is waiting in shared mode */ // 标识节点当前在共享模式下 static final Node SHARED = new Node(); /** Marker to indicate a node is waiting in exclusive mode */ // 标识节点当前在独占模式下 static final Node EXCLUSIVE = null; // ======== 下面的几个int常量是给waitStatus用的 =========== /** waitStatus value to indicate thread has cancelled */ // 代码此线程取消了争抢这个锁 static final int CANCELLED = 1; /** waitStatus value to indicate successor's thread needs unparking */ // 官方的描述是,其表示当前node的后继节点对应的线程需要被唤醒 static final int SIGNAL = -1; /** waitStatus value to indicate thread is waiting on condition */ // 本文不分析condition,所以略过吧,下一篇文章会介绍这个 static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ // 同样的不分析,略过吧 static final int PROPAGATE = -3; // ===================================================== // 取值为上面的1、-1、-2、-3,或者0(以后会讲到) // 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待, // 也许就是说半天抢不到锁,不抢了,ReentrantLock是可以指定timeouot的。。。 volatile int waitStatus; // 前驱节点的引用 volatile Node prev; // 后继节点的引用 volatile Node next; // 这个就是线程本尊 volatile Thread thread; // 这个是在condition中用来构建单向链表,同样下一篇文章中介绍 Node nextWaiter; }
Node 的数据结构其实也挺简单的,就是 thread + waitStatus + pre + next 四个属性而已,如果大家对LinkedList熟悉的话,那就更简单了,如果想了解LinkedList,可以看看我前面的文章JDK1.8源码(二)——java.util.LinkedList。
下面,我们开始说 ReentrantLock 的公平锁,首先,我们先看下 ReentrantLock 的使用方式。
// 我用个web开发中的service概念吧 public class OrderService { // 使用static,这样每个线程拿到的是同一把锁 private static ReentrantLock reentrantLock = new ReentrantLock(true); public void createOrder() { // 比如我们同一时间,只允许一个线程创建订单 reentrantLock.lock(); // 通常,lock 之后紧跟着 try 语句 try { // 这块代码同一时间只能有一个线程进来(获取到锁的线程), // 其他的线程在lock()方法上阻塞,等待获取到锁,再进来 // 执行代码... } finally { // 释放锁 // 释放锁必须要在finally里,确保锁一定会被释放,如果写在try里面,发生异常,则有可能不会执行,就会发生死锁 reentrantLock.unlock(); } } }