ReentrantLock介绍ReentrantLock 基于AQS实现了公平和非公平的独占锁功能。ReentrantLock定义AQS的同步状态(synchronization state)如下:State为0表示锁可用;为1表示被占用;为N表示锁重入的次数,是独占资源。ReentrantLock实现公平锁原理案例代码如下:

1、启动文件

public class Main { public static void main(String[] args) throws ParseException { ReentrantLock lock = new ReentrantLock(true); Thread t1 = new Thread(new Task(lock),"Thread-1"); Thread t2 = new Thread(new Task(lock),"Thread-2"); Thread t3 = new Thread(new Task(lock),"Thread-3"); t1.start(); t2.start(); t3.start(); }}

2、Task

import java.util.concurrent.locks.ReentrantLock;public class Task implements Runnable{ private ReentrantLock lock; public Task(ReentrantLock lock) { this.lock = lock; } @Override public void run() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "获取到锁...."); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread().getName() + "释放锁...."); lock.unlock(); } }}

3、执行结果:

案例分析1、Thread-1调用lock方法

ReentrantLock内部继承AQS实现了1个抽象类Sync

继承于Sync实现了2个内部类FairSync(公平的)和NonfairSync(非公平的)此时调用FairSync的lock方法

acquire方法来自AQS,注意参数是1

tryAcquire是ReentrantLock自己实现的,尝试获取锁

protected final boolean tryAcquire(int acquires) { //参数是1 final Thread current = Thread.currentThread(); //当前线程 int c = getState(); //获取当前同步状态 if (c == 0) { //如果是0,则锁没有被占用 //等待队列中,前面没有其他等待的线程,则用CAS的方法更新同步状态state if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); //成功的话,则设置锁的占有线程为当前线程 return true; //返回获取资源成功 } } //如果锁已经被占用,则判断是不是自己占用的 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires;//如果是自己占用的,则是重入,增加state值,累加1 if (nextc < 0) //重入次数过大,抛出异常 throw new Error("Maximum lock count exceeded"); setState(nextc); //设置state值 return true; //重入返回ture } return false;//没有获取资源返回false }Thread-1获取了锁资源,没有释放。2、Thread-2,开始请求资源,调用lock,此时锁资源还被Thread-1占用

addWaiter方法:

private Node addWaiter(Node mode) { //把当前线程包装成节点,准备放入等待队列 Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure //尝试直接把节点设置成队尾,否则执行enq Node pred = tail; if (pred != null) { node.prev = pred;//当前节点的上一个节点是之前的队尾节点 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //当前节点插入队尾 enq(node); return node; }enq自旋+初始化等待队列,并返回Thread-2节点

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; } } } }然后对Thread-2包装节点执行acquireQueued

final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { //判断节点的前任节点是不是头节点,头节点是一个空节点 final Node p = node.predecessor(); //如果是头节点,则说明当前节点是队列里的第一个节点,首节点。 //则尝试获取锁资源,此处因为Thread-1占用着资源,则失败 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } //失败之后,则判断当前节点线程Thread-2是不是可以阻塞 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }是否阻塞shouldParkAfterFailedAcquire

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus;//前驱节点的状态 if (ws == Node.SIGNAL) //如果是SIGNAL,则说明前驱节点状态可以唤醒后继节点,可以阻塞 /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; //只有CANCELLED状态大于0,则把取消状态的节点从队列删除 } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//设置前驱节点为SIGNAL状态 } return false; }如果可以阻塞,则调用parkAndCheckInterrupt,阻塞线程,至此Thread-2进入队列,并阻塞了,耐心等待3、Thread-3同Thread-2,略过4、Thread-1释放锁资源

release方法:

tryRelease

然后通过unparkSuccessor,唤醒首节点,保证公平策略。至此Thread-1释放完成,Thread-2可以获得资源,依次类推。