通过ReentranLock看AQS实现

通过ReentranLock看AQS实现

ReentranLock 类实现

我们常用的锁就是 ReentranLock 了,使用简单支持重入,可以支持我们大多数的场景。

Lock lock = new ReentrantLock();
// 加锁
lock.lock();
// 解锁
lock.unlock();

ReentranLock 内部有两种实现 ,公平锁 和 非公平锁。
看下类图 ,内部类 sync 有两种实现,就是我们的公平锁和非公平锁。sync集成了AQS,重写了部分方法。
****

在这里插入图片描述

看下 ReentranLock 类

   // 主要的同步功能实现 
   private final Sync sync;  

    //  默认的构造方法使用的是非公平锁的实现 
    public ReentrantLock() {
        sync = new NonfairSync();
    }
	// 通过变量来控制,是否公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
 
    // 可以看到我们的主要实现都是调用了 sync 的方法  
    public void lock() {
        sync.lock();
    }
    public void unlock() {
        sync.release(1);
    }
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

通过观察ReentranLock源码发现,主要的同步逻辑都在 Sync上。我们来逐一分析下两种锁的实现逻辑。

我们先简单介绍一下 AQS 的内部结构。 大致思想是,我们有多个线程都想要获得锁时,没获得锁的线程需要放入队列中按照先进先出进行等待加锁。同步器有头结点和尾结点的引用。Node结点:作为获取锁失败线程的包装类, 组合了Thread引用, 实现为FIFO双向队列。
在这里插入图片描述
链表初始化的头节点其实是一个虚拟节点,英文名称之为dummy header, 因为它不会像后继节点一样真实的存放线程,并且这个节点只会在真正产生竞争排队情况下才会延迟初始化,避免性能浪费,下面看代码的时候,我会再次提到。
AbstractQueuedSynchronizer 类是一个模版类,维护了着一个同步队列(双向链表),提供着同步队列一些操作的公共方法,JUC并发包里基于此类实现了很多常用的并发工具类,如 Semaphore, CountDownLatch等。

/**
     * The synchronization state. int 初始值为 0 
     */
    private volatile int state;

AbstractQueuedSynchronizer维护了一个state变量,来表示同步器的状态,state可以称为AQS的灵魂,基于AQS实现的好多JUC工具,都是通过操作state来实现的,state为0表示没有任何线程持有锁;state为1表示某一个线程拿到了一次锁,state为n(n > 1),表示这个线程获取了n次这把锁,用来表达所谓的“可重入锁”的概念。

非公平锁的加锁逻辑

lock()方法的逻辑: 多个线程调用lock()方法, 如果当前state为0, 说明当前没有线程占有锁, 那么只有一个线程会CAS获得锁, 并设置此线程为独占锁线程。那么其它线程会调用acquire方法来竞争锁(后续会全部加入同步队列中自旋或挂起)。当有其它线程A又进来想要获取锁时, 恰好此前的某一线程恰好释放锁, 那么A会恰好在同步队列中所有等待获取锁的线程之前抢先获取锁。也就是说所有已经在同步队列中的尚未被 取消获取锁 的线程是绝对保证串行获取锁,而其它新来的却可能抢先获取锁,这也是非公平锁的思想。

 static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        // ***非公平体现*** 此处第一次尝试插队  加锁时首先尝试是否可以直接CAS加锁,
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 插队不成功,走正常流程,申请获取1个资源
                acquire(1);
        }

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

acquire 方法是 AQS的实现 ,大体逻辑:tryAcquire 方法第二次尝试插队加锁和是否重入,没能获得锁返回false进行下一步。addWaiter 将 当前线程封装成 Node 节点加入等待队列的尾部。

 public final void acquire(int arg) {
     	 //  tryAcquire在 NonfairSync已经重写
     	// 1. 第二次尝试插队加锁 tryAcquire
           2. addWaiter(Node.EXCLUSIVE)  Node.EXCLUSIVE 代表独占模式 
        if (! tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

1.tryAcquire 方法第二次尝试插队加锁和是否重入,没能获得锁返回false
 protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
 }

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 第二次尝试插队加锁 
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }  // 判断当前线程是否和持有锁线程相等 重入锁  state + 1 ,前面也说了state也代表重入次数 
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
           // 没能持有锁,返回
            return false;
 }
 
 2.addWaiter 方法主要实现 将当前线程封装为 Node放置队列尾部  
   1.Node封装当前线程
   2.tail尾结点不为 null ,将node.prev 指向尾结点,然后CAS替换tail引用为自己,然后返回
   3.如果尾结点为null说明队列还未初始化
   4.CAS 替换尾结点失败,可能其他线程抢夺tali成功,则调用 enq强制入队
  private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // 获取尾结点不为 null 
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
  
  1. 判断队列是否为空,为空需要初始化队列,然后入队
  2. 不为空,for循环强制CAS入队
  private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
    
3. failed 标记最终acquire是否成功, interrupted标记是否曾被挂起过。
注意到for(;😉 跳出的唯一条件就是if (p == head && tryAcquire(arg)) 
即当前线程结点是头结点且获取锁成功。
从这里我们应该看到,这是一个线程第三次又想着尝试快速获取锁:虽然此时该节点已被加入等待队列,
在进行睡眠之前又通过p == head && tryAcquire(arg)方法看看能否获取锁。
也就是说只有该线程结点的所有 有效的前置结点都拿到过锁了,当前结点才有机会争夺锁,
如果失败了那就通过shouldParkAfterFailedAcquire方法判断是否应该挂起当前结点,
等待响应中断。观察 每次调用tryAcquire方法的时机,可以看出作者优化意图:
  final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 判断前置节点是否为head
                   是head 就会进行tryAcquire 尝试插队获得锁
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 达到一定条件会挂起,进行排队等待
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

加锁的流程大致就分为上面几步,可以看出非公平锁,会有二次以上的插队加锁情况,在一直加不上锁的情况就会串行的在队列中等待。
下面有流程图可以帮助理解

在这里插入图片描述

公平锁的加锁逻辑

公平锁和非公平锁主要的代码差异在 tryAcquire 方法中。

static final class FairSync extends Sync {

        final void lock() {
            // 不再插队加锁
            acquire(1);
        }

        /**
         * tryAcquire 也没有了插队尝试加锁的代码 
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 此处 hasQueuedPredecessor
                当前队列为空或本身是同步队列中的头结点。如果满足条件CAS获取同步状态,并设置当前独占线程
                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;
        }
    }

参考: https://blog.csdn.net/lsgqjh/article/details/63685058

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值