ReentrantLock

相对synchronized不同点

  1. 可以设置超时时间(阻塞多久获取不到锁就放弃)
  2. 可以中断(阻塞状态可以被中断放弃)
  3. 可以设置多个条件变量(wait notify相当于是一个,reentrantlock可以设置多个环境变量)
  4. 可以设置公平锁(从阻塞中唤醒竞争时可以时公平的(谁先请求,谁先获取锁))

使用举例

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

可中断

获取锁的阻塞状态可以被中断掉

try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                System.out.println("锁获取中断,退出");
                e.printStackTrace();
                return;
            }

可超时

        //获取是否加锁成功
        boolean isLock = lock.tryLock();
        //设置超时时间
        lock.tryLock(1, TimeUnit.SECONDS);

条件变量

类似synchronized的wait 和 notify ,但是更自由,不用唤醒所有。可以按condition唤醒

static Condition condition1 = lock.newCondition();
//等待
        condition1.await();
        condition1.await(1,TimeUnit.SECONDS);
//通知
        condition1.notify();
        condition1.notifyAll();

设置公平锁

唤醒阻塞中的线程可以是公平的,先进获取
static ReentrantLock lock = new ReentrantLock(true);

原理解析

类结构图如下:可以看到是基于AQS实现的


类结构图

加锁

如果没有竞争:
exclusiveOwnerThread指向加锁线程
如果有竞争,也急速cas修该state失败,则进入acqure函数:

        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

总体流程,构造Node节点,然后加入队列中(还会有好几次尝试获取锁),然后调用LockSupport.park(this);方法阻塞当前线程。
具体分析见pdf文档。

阻塞示意图

解锁

  1. 调用tryRelease方法进行锁释放
  2. 如果释放成功,判断队列head不为空且waitState不为0,进入唤醒流程unparkSuccessor
  3. 从tail往前遍历,找到最近一个需要唤醒的线程
  4. 调用LockSupport.unpark(s.thread);唤醒阻塞线程
  5. 解锁完之后,被唤醒的线程会在下一次循环中将自己从队列中删除(head指向自己,自己前驱的next为null)
final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC

可重入原理

如果获取锁的是当前线程,则state++,调用setState方法设置state

if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }

可打断原理

不可打断,发生中断时会唤醒park线程,这里清除打断标记(不清除的话,线程就无法再次park)。然后局部变量interrupted=true

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();//清除打断标记
    }

只有等线程获取到锁之后才可以感知到中断(内部变量interrupted=true时,再触发一次中断)。

static void selfInterrupt() {
// 重新产生一次中断
Thread.currentThread().interrupt();
 }

可打断

被中断唤醒之后抛出中断异常,线程结束

                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();

公平原理

多了一步判断队列中是否有等待唤醒的线程。hasQueuedPredecessors

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
              //队列中没有老二或者老二不是自己
               //也就是队列中没有要竞争的时候自己才去竞争
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }

对于非公平锁,没有队列判断,直接去竞争

  if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }

条件变量

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject(firstWaiter、lastWaiter两个节点)

await流程:

  1. 创建Node节点(和CLH队列中的Node一致),加入等待队列尾部
  2. 调用fullRelease释放锁
  3. unpark AQS 队列中的下一个节点
  4. park 阻塞 Thread-0(当前线程)


    image.png

signal流程:

  1. 取等待队列第一个Node,将Node加入AQS队列尾部

你可能感兴趣的:(ReentrantLock)