JUC锁框架——基于AQS的实现,从ReentrantLock认识独占和共享

JDK中有以下基于AQS的实现

  • ReentrantLock
  • CountDownLatch
  • Semaphore
  • ReentrantReadWriteLock
  • CyclicBarrier (委托给ReentrantLock)

在这里插入图片描述

首先关于源码中经常出现 final ReentrantLock takeLock = this.takeLock 写法:
这是一个有关volatile变量的lock-free的典型习惯编码。在第一行第一次到读到变量后,另一个线程会更新这个值,但你只对初始读到的值感兴趣。
另外,即使我们讨论的成员变量不是 volatile而是final, 这个习惯用法也与CPU缓存有关:从栈读变量比从堆读变量会更cache-friendly,本地变量最终绑定到CPU寄存器的可能性更高。

ReentrantLock的独占和共享:

基本上一致,区别:

  • 非公平锁是:
    先state+1,然后直接得到锁,
  • 公平锁是:
    先尝试去获取锁,如果得到了锁则state+1.
  • 实现公平性的关键在于:如果锁被占用且当前线程不是持有者也不是等待队列的第一个,则进入等待队列。

可见是否公平实际上是对处于等待队列中的线程来说的。

ReentrantLock都是把具体实现委托给内部类
而不是直接继承自AbstractQueuedSynchronizer,
这样的好处是用户不会看到不需要的方法,
也避免了用户错误地使用AbstractQueuedSynchronizer的公开方法而导致错误。

    // 非公平获取  
    final boolean nonfairTryAcquire(int acquires) {  
        final Thread current = Thread.currentThread();  
        int c = getState();  
        if (c == 0) {  
      // 锁是空闲的,进行加锁必须用CAS来确保即使有多个线程竞争锁也是安全的  
            if (compareAndSetState(0, acquires)) {  
                // 加锁成功  
                // 把当前线程设为锁的持有者,在获取前可用于判断是否是重入。  
                setExclusiveOwnerThread(current);  
                return true ;  
            }  
        }  
        else if (current == getExclusiveOwnerThread()) {  
             // 锁被占用且当前线程是锁的持有者,说明是重入。  
            int nextc = c + acquires;  
            if (nextc < 0)  
                   // 溢出。加锁次数从0开始,加锁与释放操作是对称的,  
                   // 所以绝不会是小于0值,小于0只能是溢出。  
                throw new Error("Maximum lock count exceeded");  
            // 锁被持有的情况下,只有持有者才能更新锁保护的资源,  
            // 所以这里不需要用CAS。  
            setState(nextc);  
            return true ;  
        }  
        return false ;  
    }  
static final class FairSync extends Sync {  
    private static final long serialVersionUID = -3000897897090466540L;  
  
    final void lock() {  
        acquire(1); //acquire会首先调用tryAcquire,所以公平策略的控制留给tryAcquire。  
    } 
    // tryAcquire的公平版本。除非是递归调用或没有等待者或者是第一个,否则不授予访问。  
    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 ;  
            }  
        }  
        else if (current == getExclusiveOwnerThread()) {  
             // 重入  
            int nextc = c + acquires;     // 重入次数增加  
            if (nextc < 0)// 溢出,重入次数太多,在改变状态之前抛出异常以确保锁的状态是正确的。  
                throw new Error("Maximum lock count exceeded");  
            setState(nextc);  
            return true ;  
        }  
        return false ;  
    }  
}  

Condition Condition

ReentrantLock除了封装Sync外,也实现了lock的接口,newCondition方法就是返回一个Condition对象

AQS等待队列与Condition队列是两个相互独立的队列

  • await()就是在当前线程持有锁的基础上释放锁资源,并新建Condition节点加入到Condition的队列尾部,阻塞当前线程
  • signal()就是将Condition的头节点移动到AQS等待节点尾部,让其等待再次获取锁

以下是AQS队列和Condition队列的出入结点的示意图,可以通过这几张图看出线程结点在两个队列中的出入关系和条件。

I.初始化状态:AQS等待队列有3个Node,Condition队列有1个Node(也有可能1个都没有)
在这里插入图片描述
II.节点1执行Condition.await()
1.将head后移
2.释放节点1的锁并从AQS等待队列中移除
3.将节点1加入到Condition的等待队列中
4.更新lastWaiter为节点1
在这里插入图片描述
III.节点2执行signal()操作
5.将firstWaiter后移
6.将节点4移出Condition队列
7.将节点4加入到AQS的等待队列中去
8.更新AQS的等待队列的tail
在这里插入图片描述

public final void await() throws InterruptedException {
    // 1.如果当前线程被中断,则抛出中断异常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 2.将节点加入到Condition队列中去,这里如果lastWaiter是cancel状态,那么会把它踢出Condition队列。
    Node node = addConditionWaiter();
    // 3.调用tryRelease,释放当前线程的锁
    long savedState = fullyRelease(node);
    int interruptMode = 0;
    // 4.为什么会有在AQS的等待队列的判断?
    // 解答:signal操作会将Node从Condition队列中拿出并且放入到等待队列中去,在不在AQS等待队列就看signal是否执行了
    // 如果不在AQS等待队列中,就park当前线程,如果在,就退出循环,这个时候如果被中断,那么就退出循环
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 5.这个时候线程已经被signal()或者signalAll()操作给唤醒了,退出了4中的while循环
    // 自旋等待尝试再次获取锁,调用acquireQueued方法
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
final boolean transferForSignal(Node node) {
    /*
     * 设置node的waitStatus:Condition->0
     */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    /*
     * 加入到AQS的等待队列,让节点继续获取锁
     * 设置前置节点状态为SIGNAL
     */
    Node p = enq(node);
    int c = p.waitStatus;
    if (c > 0 || !compareAndSetWaitStatus(p, c, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

ReentrantReadWriteLock

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值