Java-ReentrantLock-公平锁源码分析

7 篇文章 0 订阅
说明

在了解AQS后,那应该怎么了解AQS的最佳实践那,我想再也没有Java官方的实践更加优秀了,这次我打算重新拿出系统源代码,并将其总结成一系列文章,以供将来查看.

本次准备分六篇文章用来分析基于AQS实现的类

  1. 第一篇(Java-ReentrantLock-非公平锁源码分析)
  2. 第二篇(Java-ReentrantLock-公平锁源码分析)
  3. 第三篇(Java-并发工具-CountDownLatch源码分析)
  4. 第四篇(Java-并发工具-Semaphore源码分析)
  5. 第五篇(Java-ReentrantReadWriteLock-读锁分析)
  6. 第六篇(Java-ReentrantReadWriteLock-写锁分析)

介绍

本篇文章为系列文章的第二篇,本篇文章介绍ReentrantLock(可重入锁)公平锁代码实现,ReentrantLock是一个可重入的互斥锁Lock,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

什么是可重入锁

可重入锁指的是线程可以重复获取同一把锁.

概述

首先,我们从总体过程入手,了解ReentrantLock公平锁的执行逻辑,然后逐步深入分析源代码。

  1. 我们知道AQS内部是对阻塞线程队列的维护.因此,框架不支持基于优先级的同步,同时也表明加入队列的线程是按照顺序获取执行时间的,所以只有我们保证获取锁线程顺序的加入阻塞队列,我们AQS实现的锁就是公平锁,这就像我们生活中排队结账,如果保证结账的公平性,那就是有序的排队,不允许插队。

  2. 首先ReentrantLock公平锁当前线程获取状态后,不会立即尝试设置state,而是先检查是否有线程等待获取的时间长于当前线程,如果不存在才会尝试设置state(获取锁),如果存在则当前线程会执行排队.不会参与竞争锁.

源码分析

基于上面提到的过程,让我们来看看源代码实现逻辑.首先,让我们看看如果创建公平锁。

   //默认构造器 构建非公平锁
   public ReentrantLock() {
     sync = new NonfairSync();
   }
   //传值 构建 true 构建 公平锁,false 构建非公平锁     
   public ReentrantLock(boolean fair) {
     sync = fair ? new FairSync() : new NonfairSync();
   }

由上面代码可知,ReentrantLock提供了两个构造方法,默认无参构造器构建非公平锁,我们通过有参构造器传入true创建公平锁

    // 公平锁为一个内部类 ,他继承 Sync 内部类
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        //不做任何操作 直接使用AQS acquire方法
        final void lock() {
            acquire(1);
        }

        //重写 tryAcquire 
        protected final boolean tryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取当前状态
            int c = getState();
            //如果状态为0 则表示没有线程获取锁
            if (c == 0) {
                //与非公平锁不同的是,此处多了hasQueuedPredecessors()判断,该方法是实现公平锁的关键。
                //如果hasQueuedPredecessors返回true,表示有其他线程先于当前线程等待获取锁,
                //此时为了实现公平,保证等待时间最长的线程先获取到锁,不能执行CAS。
                //CAS可能会破坏公平性。反之,如果hasQueuedPredecessors返回false,则可以执行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;
            }
             //如果以上都没有设置成功
             //则返回失败 执行AQS排队机制
            return false;
        }
    }

由上面代码可知,FairSync继承SYNC,公平锁和非公平锁最大的区别就是在设置同步状态的时候,增加了一个校验hasQueuedPredecessors,该方法保证不会存在插队现象。

 //查询是否有线程等待获取的时间长于当前线程。
 public final boolean hasQueuedPredecessors() {
    
    Node t = tail; 
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

通过以上分析可以知道hasQueuedPredecessors返回true代表有别的线程在CLH队列中排了当前线程之前,返回false代表当前线程处于CLH队列的第一个线程。

  1. 分析h != t返回false的情况。此时hasQueuedPredecessors返回false。
  2. 当h和t都为null,返回false。此时说明队列为空,还从来没有Node入过队。
  3. 当h和t都指向同一个Node,也返回false。此时说明队列中只有一个dummy node,(参考AQS enq代码)那说明没有线程在队列中或者还没有线程排队成功。
  4. 分析h != t返回true 同时(s = h.next) == null 为true 此时hasQueuedPredecessors返回为true。
  5. 既然h != t返回true,说明h和t不相等,先考虑特殊情况(上面讲到的出现"head不为null,tail为null"的情况,此时head是空node,next成员肯定为null),那么说明有一个线程正在执行enq,且它正好执行到if (compareAndSetHead(new Node()))到tail = head;的间隙。但这个线程肯定不是当前线程,所以不用判断后面短路的s.thread != Thread.currentThread()了,因为当前线程连enq都没开始执行,但另一个线程都开始执行enq了,那不就是说明当前线程排在别人后面了,别的线程马上就要入队了。
  6. 既然h != t返回true,说明h和t不相等,再考虑二者都不为null。那此时队列中已经至少有一个等待中的线程了,那说明当前线程肯定排在别人后面了。

思考 hasQueuedPredecessors 结合 enq 源码思考

  1. Node t = tail; Node h = head; tail与head的赋值顺序的意义

tail 先赋值head后赋值保证了后续节点 h.next 不会为空

  • tail 为null 则 head 不为null

发生在t == null场景内, compareAndSetHead(new Node()) 刚为head赋值完成,tail 还没有被head赋值

  • tail 为null 则 head 为 null

发生在t == null场景内,compareAndSetHead(new Node()) 还没有为head赋值完成

  • tail 不为null 则 head 不为null

发生在t == null场景内,tail 被head赋值完

  • tail 不为null 则 head 为null (这种情况100%不会出现)

该场景不会发生,tail 如果不为null则,则head必然不为nul

    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;
                }
            }
        }
    }

总结

以上是ReentrantLock-公平锁的获取和释放源码分析,公平锁的实现也很简单,这些都因为它基于AQS实现,AQS已经帮我们实现了大多数功能,了解ReentrantLock源码实现能够让我们更加深入的了解AQS设计思想。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值