乐趣区

关于java:Java锁二AbstractQueuedSynchronizerReentrantLock详解

一、AbstractQueuedSynchronizer 简介

AQS(AbstractQueuedSynchronizer)是并发容器 JUC(java.util.concurrent)下 locks 包内的一个抽象类,是一个同步器,是用来构建锁或者其余同步组件的根底框架,外部保护了一个成员变量 state 示意同步状态,state=0示意线程未获取到锁,state > 0示意获取到锁,state > 1示意重入锁的数量,被 volatile润饰保障了可见性,通过 CAS 操作对其批改,内置保护了 FIFO 队列实现对未获取到锁的线程进行排队工作。

二、AbstractQueuedSynchronizer 源码解析

核心成员变量

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    
    /**
     * 期待获取锁队列的头节点, 只能通过 setHead 办法批改
     * 如果 head 存在保障 waitStatus 状态不为 CANCELLED
     */
    private transient volatile Node head;


    /**
     * 期待获取锁队列的尾节点, 只能通过 enq 办法增加新的期待节点
     */
    private transient volatile Node tail;
    
    /**
     * 示意锁的状态
     * state = 0 示意未锁定
     * state > 0 示意已锁定
     * state > 1 示意可重入锁, 获取锁的次数
     * volatile 润饰保障了可见性
     */
    private volatile int state;
}

AbstractQueuedSynchronizer 次要有三个核心成员变量stateheadtail

  • state:示意锁的状态,等于 0 示意未锁定,大于 0 示意已锁定,大于 1 示意可重入锁,重入锁的次数。被 volatile 润饰保障了可见性。
  • head:期待队列的头节点,除了初始化只能通过 setHead() 办法设置值,如果 head 存着能保障 waitStatus 状态不为CANELLED
  • tail:期待队列尾节点,只能通过 equ 增加新的期待节点。

Node 节点

AbstractQueuedSynchronizer外部保护着 FIFO 队列,也就是 CLH 队列,这个队列的每一个元素都是一个Node,所以咱们接下来要理解其余其内部类Node,源码如下:

private static class Node {
    /**
     * 节点正在共享模式下期待的标记
     */
    static final Node SHARED = new Node();

    /**
     * 节点正在独占模式下期待的标记
     */
    static final Node EXCLUSIVE = null;

    /**
     * waitStatus 变量的可选值, 勾销状态, 被勾销的节点不参加锁竞争, 状态也不会被扭转
     */
    static final int CANCELLED = 1;

    /**
     * waitStatus 变量的可选值, 下一节点处于期待状态, 如果以后节点开释锁或者被勾销, 会告诉下一节点去运行
     */
    static final int SIGNAL = -1;

    /**
     * waitStatus 变量的可选值, 示意节点处于 condition 队列中, 正在期待被唤醒
     */
    static final int CONDITION = -2;

    /**
     * waitStatus 变量的可选值, 下一次 acquireShared 应该无条件流传
     */
    static final int PROPAGATE = -3;

    /**
     * 节点的期待状态
     */
    volatile int waitStatus;

    /**
     * 上一节点
     */
    volatile Node prev;

    /**
     * 下一节点
     */
    volatile Node next;

    /**
     * 获取同步状态 (锁) 的线程
     */
    volatile Thread thread;

    /**
     * 下一个 condition 队列的期待节点
     */
    Node nextWaiter;

    /**
     * 是否是共享模式
     */
    final boolean isShared() {return nextWaiter == SHARED;}

    /**
     * 获取前一节点, 前一节点为 null 会抛异样
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null) {throw new NullPointerException();
        } else {return p;}
    }

    /**
     * 无参构造方法用于初始化头部或者共享模式标记
     */
    Node () {}

    /**
     * 用于 addWaiter 办法, 设置下一个 condition 队列的期待节点
     */
    Node(Thread thread, Node mode) {
        this.nextWaiter = mode;
        this.thread = thread;
    }

    /**
     * 用于 addConditionWaiter 办法
     */
    Node (Thread thread, int waitStatus) {
        this.thread = thread;
        this.waitStatus = waitStatus;
    }
}

外围办法

JUC 外面的工具类根本都是根底 AQS 实现的,比 ReentrantLockCountDownLatchCyclicBarrierSemaphore 等,有的只反对独占锁,如ReentrantLock#lock(),有的反对共享锁,如Semaphore,从前文的 Node 类的定义也能看到

/**
 * 节点正在共享模式下期待的标记
 */
static final Node SHARED = new Node();

/**
 * 节点正在独占模式下期待的标记
 */
static final Node EXCLUSIVE = null;

AQS 实现了两套加锁解锁的形式,那就是 独占锁 共享锁 。咱们就从 AQS 最罕用的类ReentrantLock 来学习 AQS 的外围办法。

三、ReentrantLock

简介

ReentrantLock是根底 AQS 实现的一个可重入且独占式锁。内置了一个 Sync 同步器类实现了 AQS,且反对偏心锁和非偏心锁,其实现类别离是 FairSyncNonfairSync

ReentrantLock所有操作都是通过外围外部类 Sync 操作,由子类 FairSyncNonfairSync实现。

private final Sync sync;

ReentrantLock 加锁过程

lock

lock()就是加锁,该办法定义如下:

public void lock() {sync.lock();
}

FairSyncNonfairSync 具体实现:

// FairSync 实现
final void lock() {acquire(1);
}

// NofairSync 实现 setExclusiveOwnerThread 是父类 AQS
final void lock() {if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

protected final void setExclusiveOwnerThread(Thread thread) {exclusiveOwnerThread = thread;}

能够看到非偏心锁多了一个 compareAndSetState() 操作,通过 CAS 尝试批改锁状态 state 的值,如果批改胜利设置以后线程以独占的形式获取了锁,批改失败执行的逻辑和偏心锁一样。

偏心锁和非偏心锁获取独占锁的外围逻辑都是 acquire() 办法,接下来就看看这个办法。

acquire

acquire该办法是父类 AbstractQueuedSynchronizer 定义的办法,源码如下:

public final void acquire(int arg) {if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();}

该办法次要调用 tryAcquire 办法尝试获取锁,胜利返回 true 示意获取到了锁,如果失败就将线程封装成节点插入队尾。

tryAcquire

tryAcquire办法在类 AbstractQueuedSynchronizer 没有间接实现,采纳 模版办法 的设计模式交给子类实现,先看偏心锁 FairSync 的实现,源码如下:

protected final boolean tryAcquire(int acquires) {
    // 获取以后线程
    final Thread current = Thread.currentThread();
    // 获取以后锁状态 state= 0 示意未锁定, state>0 示意已锁定
    int c = getState();
    if (c == 0) {
        // 线程没有获取到锁
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 没有比以后线程期待更久的线程了, 通过 CAS 的形式批改 state
            // 如果胜利则设置以后线程获取独占式锁
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        // 获取独占锁的线程就是以后线程, 示意重入
        // 重入锁的实现
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        // 批改 state 记录获取锁的次数
        setState(nextc);
        return true;
    }
    return false;
}

从下面源码能够看出该办法就是独占的形式获取锁,获取胜利后返回 true,重入锁的逻辑也是在这里实现,次要通过批改 state 的值来记录获取锁的次数。

非偏心锁的实现大同小异就是少了 !hasQueuedPredecessors() 的判断,因为是非偏心锁嘛,所以不须要判断阻塞工夫了。

acquire()办法除了调用 tryAcquire() 办法外还调用了 acquireQueued(addWaiter(Node.EXCLUSIVE), arg),这里有两个办法,咱们先看addWaiter() 办法。

addWaiter

该办法相当于把以后线程封装成一个节点 Node,并退出队列,这个办法咱们在下面有写过,源码如下:

/**
 * Creates and enqueues node for current thread and given mode.
 * 为以后线程和给定模式创立并设置尾节点
 * Node.EXCLUSIVE: 独占模式
 * Node.SHARED: 共享模式
 */
private Node addWaiter(Node mode) {
    // 为以后线程创立节点
    Npde node = new Node(Thread.currentThread(), mode);
    // 获取尾节点
    Node pred = tail;
    // Try the fast path of enq; backup to full enq on failure
    // 如果队列曾经创立, 尝试疾速增加尾结点
    if (pred == null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 疾速增加失败, 则调用 enq
    enq(node);
    retur node;
}
enq

enq 办法是将节点退出队列队尾,必要时要进行初始化,通过自旋 +CAS 的形式保障线程平安和插入胜利。源码如下:

/**
* Inserts node into queue, initializing if necessary. See picture above
* 将节点插入队列,必要时进行初始化
*/
private Node enq(final Node node) {
    // 自旋
    for(;;) {
        // 获取尾节点
        Node t = tail;
        // 尾节点为 null 示意队列没有初始化
        if (t == null) {
            // 设置头节点
            if (compareAndSetHead(new Node())) {tail = head;}
        } else {
            // 队列曾经初始化, 设置新增加节点的前一节点是队列的尾节点
            node.prev = t;
            // 设置尾节点
            if (compareAndSetTail(t, node)) {
                // 设置队列的尾节点的下一节点是新增加的节点, 新增加的节点就插入尾节点了
                t.next = node;
                return t;
            }
        }
    }
}

能够看出该办法就是往队列插入尾节点,通过自旋 +CAS 的形式,须要留神的是该办法返回的 Node 节点不是新插入的节点,而是新插入节点的前一节点。

enq()办法中调用的 compareAndSetHead()compareAndSetTail() 办法如下:

/**
 * 通过 CAS 设置 head 值, 只在 enq 办法调用
 */
private final boolean compareAndSetHead(Node update) {return unsafe.companreAndSwapObject(this, headOffset, null, update);
}

/**
 * 通过 CAS 函数设置 tail 值,仅仅在 enq 办法中调用
 */
private final boolean compareAndSetTail(Node expect, Node update) {return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
acquireQueued

acquireQueued()办法作用就是获取锁,如果没有获取到锁就让以后线程阻塞期待,源码如下:

/**
 * 想要获取锁的 acquire 办法,都会通过这个办法获取锁
 * 循环通过 tryAcquire 办法一直去获取锁,如果没有获取胜利
 * 就有可能调用 parkAndCheckInterrupt 办法,让以后线程阻塞
 * 后果返回 true,示意在线程期待的过程中,线程被中断了
 */
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;
                // 不须要调用 cancelAcquire 办法
                failed = false;
                return interrupted;
            }
            // 以后节点不是头节点或者没有获取到锁
            // shouldParkAfterFailedAcquire 办法用于判断以后线程是否须要被阻塞
            // 当 p 节点的状态是 Node.SIGNAL 时就会调用 parkAndCheckInterrupt 办法阻塞线程
            // parkAndCheckInterrupt 办法用于阻塞线程并且检测线程是否被中断
            // 被阻塞的线程有两种被唤醒的办法:// 1. 在 unparkSuccessor(Node node)办法,会唤醒被阻塞的 node 线程,返回 false
            // 2. 以后线程被调用了 interrupt 办法,线程被唤醒,返回 true
            // 在这里只是简略地将 interrupted = true,没有跳出 for 的死循环,持续尝试获取锁
            if (shouldParkAfterFailedAcquire(p, node) &&
               parkAndCheckInterrupt()) {interrupted = true;}
        }
    } finally {
        // failed 为 true,示意产生异样非正常退出
        if (failed) 
            // 将以后节点状态设置成 CANCELLED, 示意以后节点曾经被勾销, 不须要唤醒了
            cancelAcquire(node);
    }
}

acquireQueued办法次要流程如下:

  1. 通过 for(;;) 死循环自旋,直到 node(以后)节点获取到锁。
  2. 获取以后节点的前一个节点 p。
  3. 如果节点 p 是头节点,而后调用 tryAcquire() 尝试获取锁,如果获取胜利就将 node 节点设置成头节点而后返回。
  4. 如果节点 p 不是投节点或者获取锁失败,调用 shouldParkAfterFaildAcquired() 办法来决定是否要阻塞以后线程。
  5. 如果要阻塞以后线程,调用 parkAndCheckInterrupt() 办法阻塞以后线程。
  6. 如果以后线程产生异样,非正常退出,调用 cancelAcquire() 办法将以后节点的状态设置成勾销。
shouldParkAfterFailedAcquire

shouldParkAfterFailedAcquire()用于判断以后线程是否须要阻塞,源码如下:

/**
 * 杜绝前一个节点 pred 的状态来判断以后线程是否须要被阻塞
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取前一节点的状态
    int ws = pred.waitStatus;
    if (ws == Node.SINGAL) {
        // 如果前一节点 pred 的状态是 Node.SINGAL, 阐明以后线程须要被阻塞
        return true;
    }
    if (ws > 0) {// 如果前一节点状态是 Node.CANCELLED(大于 0 就是 CANCELLED)
        // 示意前一节点所在线程曾经被唤醒了, 要从队列中移除 CANCELLED 的节点
        // 所以从 pred 节点始终向前查找直到找到不是 CANCELLED 状态的节点, 并把该节点赋值给 node 的 prev
        // 示意 node 节点的前一节点曾经扭转
        do {node.prev = pred = pred.prev;} while(pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 此时前一节点 pred 的状态只能是 0 或者 PROPAGATE, 不可能是 CONDITION 状态
        // CONDITION(这个是非凡状态,只在 condition 列表中节点中存在,CLH 队列中不存在这个状态的节点)
        // 将前一个节点 pred 的状态设置成 Node.SIGNAL, 这样子下一次循环时, 就是间接阻塞以后线程
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

这个办法是杜绝前一个节点状态来判断以后线程是否须要被阻塞,前一节点的状态也是在这个办法中批改的,通过 compareAndSetWaitStatus() 办法。

shouldParkAfterFailedAcquire()办法次要流程如下:

  1. 如果前一节点状态是 Node.SIGNAL,则间接返回true 以后线程进入阻塞状态。
  2. 如果前一节点状态是 Node.CANCELLED(大于 0 就是 CANCELLED),示意前一个节点曾经被唤醒了,要从队列中挪动 CANCELLED 状态的节点,所以送 pred 节点始终向前查问不是 CANCELLED 状态的节点,并将该节点赋值成以后节点的前一节点,示意以后节点的前一节点发生变化,在acquireQueued() 办法中进行下一次循环。
  3. 不是后面两种状态,只能是 Node.SIGNAL 状态,批改前一节点的状态为Node.SIGNAL,下一次循环时阻塞以后线程。
parkAndCheckInterrupt

该办法用于阻塞以后线程并检测线程是否被中断,源码如下:

/**
 * 阻塞以后线程,线程被唤醒后返回以后线程中断状态
 */
private final boolean parkAndCheckInterrupt() {
    // 阻塞以后线程
    LockSupport.park(this);
    // 检测以后线程是否被中断(该办法会革除中断标识位)return Thread.interrupted();}
cancelAcquire

cancelAcquire()办法在 acquireQueued() 办法异样的时候调用,用于将以后节点的状态设置成 CANCELLED,源码如下:

// 将 node 节点的状态设置成 CANCELLED,示意 node 节点所在线程已勾销,不须要唤醒了。private void cancelAcquire(Node node) {
    // 如果 node 为 null,就间接返回
    if (node == null)
        return;

    // 将获取锁节点的线程置空
    node.thread = null;

    // 跳过那些已勾销的节点,在队列中找到在 node 节点后面的第一次状态不是已勾销的节点
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // 记录 pred 原来的下一个节点,用于 CAS 函数更新时应用
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    // 将 node 节点状态设置为已勾销 Node.CANCELLED;
    node.waitStatus = Node.CANCELLED;

    // 如果 node 节点是队列尾节点,那么就将 pred 节点设置为新的队列尾节点
    if (node == tail && compareAndSetTail(node, pred)) {
        // 并且设置 pred 节点的下一个节点 next 为 null
        compareAndSetNext(pred, predNext, null);
    } else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}
加锁过程总结
  1. 首先调用 lock() 办法,这个办法有两个子类 FairSyncNofairSync实现,示意偏心锁和非偏心锁,两个类的不同就是 NofairSync 会间接调用 compareAndSetStaus() 办法批改加锁状态,如果胜利以后线程获取到锁。
  2. 而后调用父类 AbstractQueuedSynchronizedacquire()办法获取锁。
  3. acquire()办法调用 tryAcquire() 办法尝试获取锁,tryAcquire()由子类 FairSyncNofairSync实现别离调用 fairTryAcquire()nonfairTryAcquire()办法尝试获取锁。这两个办法外面实现了重入锁的逻辑,如果以后锁状态是未获取到锁,则调用 CAS 设置锁状态,如果是获取到锁状态则会判断获取锁的线程是否是以后线程,如果是则是重入锁的逻辑记录以后线程获取锁的次数。
  4. 如果 tryAcquire() 办法调用获取锁失败,则会调用 acquireQueued() 办法再获取锁或者进入阻塞状态,acquireQueued()办法首先调用了 addWaiter() 办法用于将以后线程封装成一个节点退出队列队尾,而后再调用 acquireQueued() 办法获取锁或者进入阻塞状态,acquireQueued()办法会通过自旋的形式杜绝以后节点状态判断是否进入阻塞状态。当别的线程开释锁的时候,可能唤醒这个线程,再调用 tryAcquire() 办法获取锁。
  5. 如果产生异样,将以后节点状态设置成 CANCELLED。

ReentrantLock 开释锁过程

unlock

调用 unlock() 办法开释锁,而后调用 release() 办法,源码如下:

public void unlock() {sync.release(1);
}
release

releaseAbstactQueuedSynchronized 定义的办法用于开释锁,源码如下:

/**
 * 在独占锁模式下, 开释锁
 */
public final boolean release(int arg) {
    // 调用 tryRelease 办法尝试开释锁, 由子类实现
    if (tryRelease(arg)) {
        // 尝试开释锁胜利 获取头节点
        Node h = head;
        // 如果头节点不为 null 且状态不为勾销状态, 调用 unparkSuccessor 唤醒被阻塞的线程
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

release()开释锁流程如下:

  1. 调用 tryRelease() 办法尝试开释锁,返回 true 示意开释锁胜利,返回 false 示意还持有锁资源。
  2. 如果开释锁胜利了,且头节点不为 null,就要唤醒被阻塞的线程,调用 unparkSuccessor() 办法唤醒一个期待的线程。
tryRelease

tryRelease尝试开释锁办法是有子类实现的,上面是 ReentrantLockSynctryRelease() 办法实现:

protected final boolean tryRelease(int releases) {
    // c 示意新的锁状态
    int c = getState() - releases;
    // 如果以后线程不是获取独占锁的线程抛错
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 是否能够开释锁
    boolean free = false;
    // 如果新的锁状态 = 0 示意能够开释锁
    if (c == 0) {
        free = true;
        // 获取独占锁的线程置为 null
        setExclusiveOwnerThread(null);
    }
    // 锁状态设置成未锁定
    setState(c);
    return free;
}

tryRelease()尝试开释锁流程如下:

  1. 首先获取新的锁状态
  2. 判断以后线程是否是获取独占锁的线程,如果不是抛异样。
  3. 如果新的锁状态是未锁定状态,获取独占锁的线程置为 null,新的锁状态置为未锁定。
unparkSuccessor

unparkSuccessor()办法用于唤醒 node 节点下一节点非勾销状态的节点所在线程,源码如下:

/**
 * 唤醒 node 节点下一节点非勾销状态的节点所在线程
 */
private void unparkSuccessor(Node node) {
    // 获取 node 节点的状态
    int ws = node.waitStatus;
    // 如果状态小于 0, 就将状态置为 0, 示意这个 node 节点曾经实现了
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 获取节点下一节点
    Node s = node.next;
    // 如果下一节点为 null, 或者下一节点状态为勾销状态, 就要寻找下一个非勾销状态的节点
    if (s == null || s.waitStatus > 0) {
        // 先将 s 设置成 null, s 不是非勾销状态的节点
        s = null;
        // 
        for(Node t = tail; t != null && t!= node; t = t.prev)
            // 因为是从后向前遍历,所以一直笼罩找到的值,这样能力失去 node 节点后下一个非勾销状态的节点
            if (t.waitStatus <= 0)
                s = t;
        // 如果 s 不为 null,示意存在非勾销状态的节点, 那么调用 LockSupport.unpark 办法唤醒这个节点的线程    
        if (s != null)
            LockSupport.unpark(s.thread);
    }
}

unparkSuccessor()办法唤醒 node 节点的下一个非勾销状态的节点所在线程流程如下:

  1. 先将 node 节点的状态设置为 0。
  2. 寻找下一个状态不为勾销的节点 s。
  3. 如果节点 s 不为 null,调用 LockSupport.unpark() 办法唤醒 s 所在线程。
开释锁过程总结
  1. 先调用 tryRelease() 办法尝试开释以后持有的锁资源。
  2. 如果胜利开释了锁资源,则调用 unparkSuccessor() 办法去唤醒一个期待锁的线程。

四、总结

到这里 ReentrantLock 加锁开释锁的过程曾经学习结束,ReentrantLock是基于 AQS 实现的 独占式锁 ,外部保护了一个FIFO 队列 实现未获取到锁的线程进行排队工作,ReentrantLock外部有 FairSync(偏心锁)和NonfairSync(非偏心锁)两种实现,通过调用lock() 办法加锁,调用 unlock() 办法解锁。

五、本人实现一个可重入的独占锁

通过继承 AbstractQueuedSynchronizer 类重写 tryAcquire()tryRelease()办法实现自定义的可重入独占锁。

代码如下:

public class SyncLock extends AbstractQueuedSynchronizer {

    @Override
    protected boolean tryAcquire(int acquires) {
        // 获取以后线程
        final Thread current = Thread.currentThread();
        // 获取以后锁状态
        int c = getState();
        // 如果锁状态为 0, 示意以后锁是闲暇的
        if (c == 0) {
            // 调用 CAS 原子操作设置锁状态
            if (compareAndSetState(0, acquires)) {
                // 如果设置胜利, 将以后线程设置为获取独占锁的线程
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // 判断以后线程是不是获取独占锁的线程, 因为可能重入锁
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) {throw new Error("Maximum lock count exceeded");
            }
            // 重入锁实现逻辑, 记录获取锁的次数
            setState(nextc);
            return true;
        }
        return false;
    }

    @Override
    protected boolean tryRelease(int releases) {int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0){
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

}

class AQSTest {public static void newThread(SyncLock syncLock, String name, int time) {new Thread(new Runnable() {
            @Override
            public void run() {System.out.println("线程" + Thread.currentThread().getName() +
                        "开始运行, 筹备获取锁。");
                syncLock.acquire(1);
                try {System.out.println("线程" + Thread.currentThread().getName() + ", 在 run 办法获取了锁。");
                    lockAgain();
                    try {Thread.sleep(time);
                    } catch (InterruptedException e) {e.printStackTrace();
                    }
                } finally {System.out.println("线程"+Thread.currentThread().getName()+"在 run 办法中开释了锁。");
                    syncLock.release(1);
                }
            }
            private void lockAgain() {syncLock.acquire(1);
                try {System.out.println("线程" + Thread.currentThread().getName() + ", 在 lockAgain 办法获取了锁。");
                    try {Thread.sleep(10);
                    } catch (InterruptedException e) {e.printStackTrace();
                    }
                } finally {System.out.println("线程"+Thread.currentThread().getName()+"在 lockAgain 办法中开释了锁。");
                    syncLock.release(1);
                }
            }
        }, name).start();}

    public static void main(String[] args) {SyncLock syncLock = new SyncLock();
        newThread(syncLock, "t1111", 1000);
        newThread(syncLock, "t2222", 1000);
        newThread(syncLock, "t3333", 1000);
        newThread(syncLock, "t4444", 1000);
    }
}

下面代码测试后果如下:

线程 t1111 开始运行, 筹备获取锁。线程 t2222 开始运行, 筹备获取锁。线程 t1111, 在 run 办法获取了锁。线程 t1111, 在 lockAgain 办法获取了锁。线程 t4444 开始运行, 筹备获取锁。线程 t3333 开始运行, 筹备获取锁。线程 t1111 在 lockAgain 办法中开释了锁。线程 t1111 在 run 办法中开释了锁。线程 t2222, 在 run 办法获取了锁。线程 t2222, 在 lockAgain 办法获取了锁。线程 t2222 在 lockAgain 办法中开释了锁。线程 t2222 在 run 办法中开释了锁。线程 t4444, 在 run 办法获取了锁。线程 t4444, 在 lockAgain 办法获取了锁。线程 t4444 在 lockAgain 办法中开释了锁。线程 t4444 在 run 办法中开释了锁。线程 t3333, 在 run 办法获取了锁。线程 t3333, 在 lockAgain 办法获取了锁。线程 t3333 在 lockAgain 办法中开释了锁。线程 t3333 在 run 办法中开释了锁。

六、ReentrentLock 和 synchronized 的比拟

相同点:
  • 都是加锁形式同步
  • 都是重入锁。
  • 都是通过阻塞的形式实现同步。
不同点
  • 原始形成:synchronized是 java 语言的关键字,是原生语法层面的互斥,由 JVM 实现,而 ReentrentLock 是 JDK1.5 之后提供的 API 层面的互斥锁。
  • 实现:synchronized是通过 JVM 实现加锁解锁,而 ReentrentLock 是 API 层面的加锁解锁,须要手动解锁。
  • 代码编写:synchronized不须要手动开释锁,润饰办法或者代码块,而 ReentrentLock 必须手动开释锁,如果没有开释锁可能造成死锁景象。须要 lock()unlock()办法配合 try/finally 语句块实现。
  • 灵活性:synchronized只能用于润饰办法或者代码块,灵活性低,而 ReentrentLock 是办法调用能够跨办法,灵活性高。
  • 是否期待可中断:synchronized不可中断,除非抛出异样,而 ReentrentLock 是能够中断的,如果持有锁的线程长期不开释锁,正在期待的线程能够抉择放弃期待,通过设置超时工夫办法。
  • 是否偏心锁:synchronized是不偏心锁,而 ReentrentLock 是可偏心锁也可不偏心锁。
  • 实现原理:synchronized是通过编译,会在同步代码块前后别离生成 monitorentermonitorexit两个指令实现同步,在执行 monitorenter 的指令时会尝试获取锁,获取锁胜利会通过计数器 +1,执行结束之后会执行 monitorexit 执行计数器 -1,当计数器为 0 时开释锁,如果获取锁失败就会进入阻塞状态,而 ReentrentLock 是通过 CAS + CLH 队列 实现,通过 CAS 原子性操作实现对锁状态 state 的批改,通过 CLH 队列 实现对未获取到锁的线程进行排队工作。
退出移动版