我先给出一个demo, 这样你们就能够根据我给的这段代码, 边调试边看源码了. 仍是那句话: 注意"My" , 我把ReentrantLock类 更名为了 "MyReentrantLock"类 , "Lock"类 更名为了"MyLock"类. 你们粘贴个人代码的时候, 把相应的"My"都去掉就行了, 不然会编译报错哦.java
import java.util.Scanner; import java.util.function.Supplier; public class Main { static final Scanner scanner = new Scanner(System.in); static volatile String cmd = ""; private static MyReentrantLock lock = new MyReentrantLock(true); public static void main(String[] args) { for (String name : new String[]{"1", "2", "3", "4", "5", "6"}) new Thread(() -> func(() -> lock, name)).start(); while (scanner.hasNext()) { cmd = scanner.nextLine(); } } public static void func(Supplier<MyLock> myLockSupplier, String name) { blockUntilEquals(() -> cmd, "lock " + name); myLockSupplier.get().lock(); System.out.println("获取了" + name + "号锁"); blockUntilEquals(() -> cmd, "unlock " + name); myLockSupplier.get().unlock(); System.out.println("释放了" + name + "号锁"); } private static void blockUntilEquals(Supplier<String> cmdSupplier, final String expect) { while (!cmdSupplier.get().equals(expect)) quietSleep(1000); } private static void quietSleep(int mills) { try { Thread.sleep(mills); } catch (InterruptedException e) { e.printStackTrace(); } } }
使用例子在下面. 首先线程1申请了锁, 成功申请. 而后线程2申请了锁, 未申请到, 进入等待队列中. 线程3 和 线程4 也申请失败, 进入到等待队列中. ui
随后释放了锁1, 而后锁2就获取到锁了. 而后释放了锁2, 锁3就获取到锁了...而后是锁4. 大概就是这个使用. 用个人这段代码配合着debug, 能够很清楚地调试出代码的执行流程.线程
一个ReentrantLock()实例里只有一个sync成员变量.debug
假设我们建立了一个公平锁, 那么sync是FairSync类的实例.3d
sync实例里面有四个成员变量.指针
分别表示:调试
1. state - 锁计数器blog
2. exclusiveOwnerThread - 锁的持有线程队列
3. head - `等待队列`的头结点.get
4. tail - 指向`等待队列`的最后一个元素
如今锁是空闲状态.
当线程1申请了锁, 会把state置为1. 而后把锁的exclusiveOwnerThread指向本身(线程1). 这就算是持有锁了.其余线程没法再获取锁了.只能等线程1释放.
若是线程1在此对这个锁执行了lock()方法呢?
那么就是锁的重入了, 也就是说这个线程再次进入(获取)了这个锁 会让state+1.
再重入呢? 那就再加1....
能够重入多少次呢? 能够重入, 直到整形int溢出为止...
接下来, 线程1还没释放锁呢, 线程2就想获取锁了. 那么会发生什么呢:
把线程2封装为一个Node类型的节点. 而后打算把这个Node放到`等待队列`中去.
这个时候`等待队列`才会被创建, 由于这个时候才须要`等待队列`, 这种叫懒初始化.
这个时候, `等待队列`的头结点产生了. 而后把`等待队列`的tail也指向head.
head或者tail 不为null, 表示`等待队列`被创立了.
head==tail 表示, `等待队列`为空, 里面没有`有效元素`.
`等待队列`有了. 线程2对应的Node也有了. 就差把这个Node插入到队尾了.
首先让tail指向线程2对应的Node.
而后分别维护两个Node的前驱和后继.(看下面紫色箭头)
已经将线程2对应的Node插入到`等待队列`的尾部了, 接下来让线程1对应的Node里的waitState等于-1
以后线程2就能够安心的挂起了. 等线程1彻底释放锁的时候, 就会唤醒线程2了.
为何说是`彻底释放`呢? 由于锁的的state如今等于3. 须要线程1 unlock()释放3次锁, 才算是彻底释放.
接下来, 线程1还没释放锁呢, (线程2也没轮到锁呢). 线程3就想获取锁了. 那么会发生什么呢:
首先会建立一个线程3对应的Node节点.
而后让尾指针tail指向这个最新的Node.
而后维护前驱和后继(紫色箭头), 来维持双向链表.
接下来就会让新节点的前驱节点的waitStatus = -1.
-1表示, 有下一个节点等待被唤醒.
而后线程3就能够安心的挂起了.
等线程2 抢到锁, 用完了释放后, 就会去唤醒线程3.
我们让线程1 unlock() 一次.
state减1了.
此时, 锁并无释放, 仍是被线程1持有.
我们再让线程1 unlock() 一次.
state减1了. 但仍然大于0.
此时, 锁并无释放, 仍是被线程1持有.
我们再让线程1 unlock() 一次.
state减1了. 这回state等于0了. 表示彻底释放了锁.
exclusiveOwnerThread也置为了null, 表示当前的锁不被任何线程持有.
准备唤醒下一个, 也就是`等待队列`的第一个元素(线程2)
线程2被唤醒
而后锁的state被置为了1.
锁的exclusiveOwnerThread指向了线程2. 表示当前锁被线程2持有了.
既然线程1已经彻底释放锁了. 那么就换线程2来当`等待队列`的头结点.
因此此时, 头结点的含义就是: 当前持有锁的线程对应的Node结点.
而后断开相应的前驱和后继, 让线程1对应的Node彻底脱离`等待队列` .
到此, 线程1释放后, 线程2 获取锁的步骤就都执行完了.
接下来, 我们让线程2释放锁.
state减1后等于0了.
因而锁就彻底释放了. exclusiveOwnerThread就被置为null了.
而后是waitStatus被改回了0. 线程2对应的Node立刻就要离开`等待队列`了
线程3被唤醒.
让state=1, 并把锁的exclusiveOwnerThread指向本身. 表示线程3本身独占了这把锁.
修改head指针, 并断开相应的前驱和后继连接, 让线程2对应的Node完全离开`等待队列`
最后, 我们让线程3释放锁.
state归零.
exclusiveOwnerThread清空.
锁空闲.
而head和tail仍然指向原先的Node. 之后等待队列的头结点就不须要从新初始化了.