# 1.ReentrantLock特点

  • 可中断
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) {
    Thread thread = new Thread(() -> {
        //如果没有竞争此方法会获得lock锁,如果有竞争,就进入阻塞队列,
        //可以被其他线程用interrupt方法打断
        try {
            System.out.println("尝试获取锁");
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("没有获取到锁");
            return;
        }

        try {
            System.out.println("获取到了锁");
        } finally {
            lock.unlock();
            ;
        }
    });

    lock.lock();
    thread.start();

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    thread.interrupt();

}

在ReentrantLock中,lockInterruptibly()方法是获得锁,但是它是可以打断的,什么意思呢,就是在上面的例子中,肯定是主线程获取到了锁,然后thread线程开始执行,这个时候因为这俩个线程公用的是同一把锁,因此thread线程肯定是获取不到锁了,1秒后,调用thread线程的interrupt方法,意思就是来打断,这个时候thread感知到自己被打断了,就抛出异常,进入catch块中。lockInterruptibly()是可打断活的锁,而lock()方法获得锁是不能打断的
执行结果

image.png

  • 可以设置超时时间:ReentrantLock的tryLock()的方法是尝试获取锁,里面还可以通过设置参数进行超时等待,例如lock.tryLock(1, TimeUnit.SECONDS)代表尝试等待1秒后获取锁。
  • 可以设置为公平锁:ReentrantLock默认是不公平锁,通过构造方法设置是否公平。
  • 支持多个条件变量
  • 与synchronized一样,都支持可重入

# 2.基本语法

// 获取锁
  reentrantLock.lock();
  try {
// 临界区
 } finally {
// 释放锁
   reentrantLock.unlock();
 }

# 3.ReentrantLock原理

# 3.1非公平锁实现原理

  • 加锁解锁流程
    • 先从构造器开始看,默认为非公平锁实现
/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

可以看到同步器对象,默认用的是NonfairSync();非公平锁

  • 当调用lock方法时:

/**
 * Acquires the lock.
 *
 * <p>Acquires the lock if it is not held by another thread and returns
 * immediately, setting the lock hold count to one.
 *
 * <p>If the current thread already holds the lock then the hold
 * count is incremented by one and the method returns immediately.
 *
 * <p>If the lock is held by another thread then the
 * current thread becomes disabled for thread scheduling
 * purposes and lies dormant until the lock has been acquired,
 * at which time the lock hold count is set to one.
 */
public void lock() {
    sync.lock();
}

调用的是同步器的lock()方法


/**
 * Sync object for non-fair locks
 */
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

image.png 当没有竞争时,就直接将它的exclusiveOwnerThread设置为当前线程,并将state设置为1

image.png

# 加锁失败流程

第一个竞争出现时,
这时候肯定就进入acquire(1)方法

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

Thread-1 执行了

  1. CAS 尝试将 state 由 0 改为 1,结果失败
  2. 进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败
  3. 接下来进入 addWaiter 逻辑, 构造 Node 队列
    • 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态
    • Node 的创建是懒惰的
    • 首次会创建俩个节点,其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程,第二个节点就是Thread-1节点,双向链表相互关联

image.png

private Node addWaiter(Node mode) {
//给当前线程创建一个节点
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    //创建一个
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

当前线程进入 acquireQueued 逻辑

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        
        for (;;) {
        //获取当前Thread-1的前驱节点也就是刚才那个站位节点
            final Node p = node.predecessor();
            //如果你这个节点是站位节点,会在做一次尝试。
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //这个方法就是当尝试失败的时候是否应该park阻塞住
           
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
  2. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
  3. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,为什么设置为-1,在这里面设置为-1就代表你有义务去唤醒下一个节点,这次返回 false

image.png 4. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败 5. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true 6. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

image.png 再次有多个线程经历上述过程竞争失败,变成这个样子

image.png

# 释放锁流程

unlock方法其实就是

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
protected final 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;
}

# 释放锁成功流程

Thread-0 释放锁,进入 tryRelease 流程,如果成功
设置 exclusiveOwnerThread 为 null
state = 0

image.png 当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程
找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
回到 Thread-1 的 acquireQueued 流程

image.png 如果加锁成功,会设置

  • exclusiveOwnerThread为Thread-1,state=1
  • head指向刚刚Thread-1所在的node,该Node情况Thread
  • 原本的heand因为从链表断开,而可被垃圾回收。

# 释放锁失败

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了

image.png 如果不巧又被 Thread-4 占了先

  • Thread-4 被设置为 exclusiveOwnerThread,state = 1
  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

# 总结

ReentranLock的加锁流程,当线程1进来时,没有线程竞争的时候,他就会将exclusiveOwnerThread设置成自己,并将state设置1,就当下一次有线程进来的时候,告诉对方,这个锁现在被人持有了,然后线程2进来时,发现这个锁正在被别人持有,会再次尝试一下,如果还是失败,会通过addWaiter创建一个双向链表,首次创建俩个节点,第一个是站位节点,第二个是线程2的节点,这个时候这俩个节点状态都为0。正常状态,创建成功之后,会进入acquireQueued方法死循环不断尝试获得锁,尝试几次以后都失败了,进入park阻塞了,进入 shouldParkAfterFailedAcquire将第一个节点waitState改为-1.就代表这个节点有义务去唤醒下一个节点。然后如果有多个线程都来获取锁,都失败了,会一个一个排在后面,并且都将前一个节点waitestate改为-1.
解锁流程:当一个线程调用unpark释放锁时,将exclusiveOwnerThread设置为null,state=0当队列不为空时,且状态为-1时就代表这个队列里面有需要唤醒的线程。这个时候找到队列里还在排队的最近的线程(Thread1)。恢复运行。这个时候exclusiveOwnerThread就变成了Thread1,state=1.队列中head节点就指向了Thread1节点,刚刚那个节点就断开了,被回收掉了。
如果刚释放锁恰巧来了新的线程(Thread8),这里也就是非公平锁的体现。有可能这个Thread8直接就占有了锁,exclusiveOwnerThread设置为Thread8,state1,这个时候Thread1再次进入acquireQueued,获取锁失败,进入阻塞状态