AbstractQueuedSynchronizer之ReentrantLock

AQS:AbstractQueuedSynchronizer(字面翻译 抽象的 排队的 同步器,即同步器框架)

AQS定义了多线程下,访问共享资源的同步器框架

AQS具备的特性

  1. 阻塞等待队列
  2. 共享/独占
  3. 公平/非公平
  4. 可重入
  5. 允许中断

初识AbstractQueuedSynchronizer之ReentrantLock

想象一下如果自己要实现一个锁机制,要能做到什么?

同一时间,只有一个线程能访问到公共资源的功能。持有线程操作完以后,其他线程继续竞争
如果不用synchronized,如何实现呢?

实现步骤

  1. 创建一个对象,有两个方法,加锁、解锁
  2. 对象中有如下属性:
    • state(状态):记录当前是否有线程占用锁
    • 队列:没有竞争到锁的对象放到队列中
  3. Lock时修改state状态(要保证只有一个线程能修改成功)
  4. 没有修改成功的线程放到队列中,并线程阻塞
  5. unlock时,修改状态,并从队列中取出一个线程解除阻塞,并去获取锁

开始学习ReentrantLock

通过上面分析就是ReentrantLock实现锁机制的大体流程,为了实现这些,用到了:

  1. compareAndSwapInt: 多线程修改对象中状态时,保证只有一个线程能修改成功
  2. for ( ; ; ) {//业务} :自旋,保证多线程入队的时候,最终入队成功
  3. head、tail:头节点与尾节点,队列的开始位置与结束位置
  4. LockSupport.park(this):阻塞线程
  5. LockSupport.unpark(s.thread):释放阻塞
  6. Node节点中的waitStatus属性:记录线程状态(0-初始化、CANCELLED-中断或者取消、SIGNAL-可竞争锁),根据此状态判断队列中的线程是否健康的,是否可以竞争锁 还是抛弃

ReentrantLock实现流程

粉色代码是ReentrantLock代码,绿色代码是AbstractQueuedSynchronizer代码
在这里插入图片描述

代码分析

lock方法

  1. tryAcquire:尝试获取锁
  2. addWaiter:未获取到锁,添加到队列中
  3. acquireQueued:自旋【cas获取锁+阻塞线程(等待唤醒)】
tryAcquire:尝试获取锁**
/**
 * 尝试获取锁
 *
 * 判断是否有线程持有锁,如果state=0.表示没有线程持有锁
 * 没有线程持有锁的话,判断队列是否有数据,如果等待队列中没有数据,尝试CAS方式修改state(抢锁)
 * 如果设置状态成功(抢锁成功),设置持有锁对象为当前线程
 * 如果当前有线程持有锁,判断是否是当前线程持有的
 * 如果是当前线程持有锁,修改获取锁次数(单线程操作,不会有并发问题,不需要CAS)
 **/
 @Override
 protected final boolean tryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
     if (c == 0) {
     	 //hasQueuedPredecessors判断队列是否有数据
         if (!hasQueuedPredecessors() && 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;
 }
}
addWaiter:当前线程放到队列中(独占模式)
/***
 * 根据当前线程创建一个node节点,并添加到CLH队尾
 *
 * 将当前线程包装成node对象,并设置成独占模式
 * 判断队列中是否有数据:取出队尾,如果队尾不是空,表示有数据,尝试对象入队尾
 * 将当前节点的前驱节点指向队尾,并使用CAS操作更新属性
 * 如果设置失败,使用enq自旋方式一直尝试入队
 **/
private Node addWaiter(Node mode) {
    // 将当前线程包装成node对象,并设置成独占模式
    Node node = new Node(Thread.currentThread(), mode);
    // 取出队尾节点
    Node pred = tail;
    // 队尾不是空
    if (pred != null) {
        // 当前节点前驱节点为队尾节点
        node.prev = pred;
        // CAS方式更新字段
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 入队失败,自旋方式一直入队,直到入队成功
    enq(node);
    return node;
}
enq:自旋+cas入队,直到入队成功
/***
 * 将节点添加到CLH同步等待队列中
 *
 * 自旋 一直尝试往队列中追加,直到入队成功
 * 队列无数据时,需初始化,使用CAS操作,初始化head节点,并设置头尾同指向同一节点
 * 进入第二次循环,将当前节点前驱点设置成尾节点
 * 使用CAS操作设置tail节点为当前节点(入队尾)
 * 返回尾节点
 **/
private Node enq(final Node node) {
    for (;;) {
        // 取队尾数据
        Node t = tail;
        // 队尾是空,队列无数据
        if (t == null) {
            // 初始化头部节点
            if (compareAndSetHead(new Node())) {
                // 第一次初始化的时候,头部和尾部一样
                tail = head;
            }
        } else {
            // 如果已经初始化过了,将当前节点插入队尾
            // 当前节点的前驱节点设置成尾节点,表示插入CLH队尾
            node.prev = t;
            // 将当前节点设置为tail 尾部节点
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
acquireQueued:准备阻塞线程
/***
 * 自旋+cas获取锁+阻塞
 *
 * 判断当前节点的前驱节点如果是头部节点,表示当前线程是队列中的第一个,再尝试获取下锁
 * 如果前驱节点不是头部节点,判断前驱节点状态,如果前驱节点
 * 将当前线程的节点的前驱节点设置成健康的节点,
 * 并设置前驱节点状态为signal
 * 阻塞当前线程
 **/
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;
                failed = false;
                // 返回失败
                return interrupted;
            }
            // 如果当前节点可以接收信号,阻塞当前线程
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
                interrupted = true;
            }
        }
    } finally {
        if (failed) {
            cancelAcquire(node);
        }
    }
}

unLock方法

  1. tryRelease:尝试释放锁
  2. unparkSuccessor:如果当前线程持有锁全部释放,唤醒队列中第一个节点
/***
 * 释放锁
 * 状态-释放锁数量
 * 如果非当前线程操作释放锁,报错
 * 如果当前线程的锁都释放完了,清空线程记录
 * 如果全部释放了,返回true,否则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;
}
/***
 * 唤醒线程 
 *
 * 取节点的状态,判断状态是否正常,为SIGNAL则设置状态为0
 * 取节点的下一个节点,如果下一个节点状态是无效的,
 * 则从队尾一直往前取,取到离当前节点最近的健康的节点
 * 用取出的节点,唤醒此节点对应的线程
 **/
    /***
     * 唤醒线程 TODO
     *
     * 判断队列中头节点状态是否是可以接收信号状态
     * 如果是,使用CAS修改为0
     * 取出队列中第一个节点唤醒此节点
     *
     **/
    private void unparkSuccessor(Node node) {
        // 节点释放阻塞
        // 判断节点状态,如果小于0,节点设置成0,
        int ws = node.waitStatus;
        if (ws < 0) {
            compareAndSetWaitStatus(node, ws, 0);
        }
        // 取出队列中第一个节点唤醒此节点
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev) {
                if (t.waitStatus <= 0) {
                    s = t;
                }
            }
        }
        // 如过有需要唤醒的节点
        if (s != null) {
            // 唤醒节点
            LockSupport.unpark(s.thread);
        }
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值