JUC并发阅读-ReentrantLock

阅读ReentrantLock首先需要看Lock接口,可以根据接口中的方法,大概了解ReentrantLock的功能是什么,观察接口,有以下几种方法:

void lock(); ------ 获得锁,在一个JVM中同步线程之间的操作
void lockInterruptibly() throws InterruptedException; ----获得可被打断的锁,可以通过线程的interrupt对锁进行打断
boolean tryLock(); ---- 非阻塞获取锁,若没有获得立刻返回状态false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; ---- 在一段时间内尝试获取锁
void unlock(); ---解除锁
Condition newCondition(); ---- 条件队列获取

ReentrantLock有属性Sync,完成加锁操作都是通过属性Sync完成,可以看到Sync是ReentrantLock中定义的一个内部类,继承了AbstractQueuedSynchronizer,在AbstractQueuedSynchronizer中,定义了属性state,属性state采用volatile修饰.ReentrantLock保证线程的可见性利用volatile进行保证,volatile修饰的变量遵循mesi协议,通过jvm层面的内存屏障,底层的lock前缀指令,保证每次修改值后,必须立刻同步回主内存;每次使用前,必须先从主内存刷新最新的值。

AbstractQueuedSynchronizer定义了Node节点的数据类型,为一个双向链表,在内部有两个属性,头节点Node,尾节点Node

AbstractQueuedSynchronizer定义了两个方法,addWaiter方法,入队方法,保证线程安全的进入阻塞队列,串行等待线程释放

源码阅读开始,我们从构造方法开始看起,主要看非公平锁,公平锁下章看

public ReentrantLock() {//ReentrantLock的构造方法,在无参构造方法时候利用的是非公平同步器,那么我们获取的也就是非公平锁
   sync = new NonfairSync();
}

下面我们看加锁的源码,可以通过源码了解源码作者的加锁逻辑,非公平加锁的实际逻辑比较容易看,就是利用cas进行状态的改变,若加锁成功对线程进行设置,否则就尝试入队

public void lock() {//调用lock方法,获取锁,看到是利用内部类Sync进行加锁的
   sync.lock();
}

-------------------------------------------------------------
final void lock() {//非公平加锁的逻辑,首先尝试修改state状态,通过状态标记加锁成功,如果修改状态没有成功,那么通过acquire(1)入队阻塞等待锁的释放
  if (compareAndSetState(0, 1))//首先线程通过cas[比较与交换]改变state状态进行加锁
      setExclusiveOwnerThread(Thread.currentThread());//成功就将当前线程设置为当前获取锁的线程
  else
      acquire(1);//cas失败,加锁没有成功,入队阻塞等待
}

下面我们跟如acquire方法,这个算是加锁中最精髓的地方了

public final void acquire(int arg) {
  if (!tryAcquire(arg) &&
     acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//条件判断:首先利用tryAcquire尝试获取锁,未成功利用addWaiter入队,最后通过acquireQueued阻塞等待
     selfInterrupt();
}
----------------------------------------------------------------
因为我们看的非公平锁,所以tryAcquire是非公平锁的实现
final boolean nonfairTryAcquire(int acquires) {
  final Thread current = Thread.currentThread();//获取当前尝试获取锁的线程
  int c = getState();//获取当前锁的状态
  if (c == 0) {//如果状态c==0,表示锁没有被其他线程获取,可以尝试获取
    if (compareAndSetState(0, acquires)) {//这里利用cas是因为考虑线程的暂停性,可能在判断c==0进入cas之前线程暂停,突然有线程进来直接加上锁,那么cas就失败,直接返回false,表示加锁失败
       setExclusiveOwnerThread(current);//加锁成功,设置当前线程,返回成功
       return true;
     }
 }
 else if (current == getExclusiveOwnerThread()) {//重入逻辑,表示当前就是获取锁的线程,那么就将重入次数加1,这里不用cas进行加1是因为此时线程已经获取锁,对于这把锁只有这个线程运行,不存在线程安全问题
     int nextc = c + acquires;
     if (nextc < 0) // overflow
       throw new Error("Maximum lock count exceeded");
       setState(nextc);
       return true;
     }
//如果非重入,c!=0或者c==0时但cas失败,都会进入这里返回false表示加锁失败
    return false;
 }

若tryAcquire返回true,那么!tryAcquire(arg)就为false,短路原理,直接退出判断条件,若tryAcquire返回false,则接着执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法

这个方法我们就从addWaiter(Node.EXCLUSIVE)开始看,这里Node.EXCLUSIVE表示这个节点是独占节点,也表示这把锁是独占锁,只能一个线程占有,也是悲观锁的体现

private Node addWaiter(Node mode) {//这个方法是AQS中的通用方法
   Node node = new Node(Thread.currentThread(), mode);//首先构造独占模式的节点
   // Try the fast path of enq; backup to full enq on failure
   Node pred = tail;//获取双向链表的尾部
   if (pred != null) {//尾部不为空
      node.prev = pred;//将尾部设为新创建独占模式节点的前驱节点
      if (compareAndSetTail(pred, node)) {//通过cas将新创建节点设置为尾节点
         pred.next = node;//cas失败,则将原尾部节点的后置节点设置为新创建节点
         return node;//返回新创建的节点
        }
      }
        enq(node);//cas失败,那么久通过循环+cas进行入队
        return node;//返回新创建的节点
 }
----------------------------------------------------------------------------------------
private Node enq(final Node node) {//进入的是新创建的独占模式节点
  for (;;) {//死循环开始
     Node t = tail;//获取尾节点
     if (t == null) { //尾部节点为null,那么表示双向链表还没有进行初始化,通过cas进行初始化
       if (compareAndSetHead(new Node()))//cas进行初始化头部节点
           tail = head;
       } else {//尾部节点不为null
           node.prev = t;//将独占节点的前驱节点设置为尾节点
           if (compareAndSetTail(t, node)) {//cas设置独占节点为尾节点,考虑线程的暂停性,在此步暂停,那么就会设置失败,这时死循环的作用体现,无法return接着循环获取最新的尾节点,直到cas成功
              t.next = node;//设置成功,那么将原尾节点的后置节点设置为独占节点
              return t;//返回新的独占节点
      }
   }
 }
-----------------------------------------------------------------------------------------
final boolean acquireQueued(final Node node, int arg) {//node为新new的且已经入队的独占节点
   boolean failed = true;//设置失败标志为true
   try {
     boolean interrupted = false;//设置中断标志为false
     for (;;) {
       final Node p = node.predecessor();//获得前驱节点
       if (p == head && tryAcquire(arg)) {//如果前驱节点为头节点,注意以下,头节点为一个线程为null的节点,可以这么理解,排队买票,头节点是正在买票的,表示正在运行的线程,如果尝试获取锁成功,进行设置
          setHead(node);
          p.next = null; // help GC
          failed = false;
          return interrupted;
        }
        if (shouldParkAfterFailedAcquire(p, node) &&
            parkAndCheckInterrupt())//第一个方法设置前置节点的waitstatus,首先将前驱节点的waitstatus由0设置为-1,表示可以自己可以被唤醒,第一次由0设置-1,会在循环一次走到这个方法返回true,然后通过parkAndCheckInterrupt进行阻塞
            interrupted = true;
        }
        } finally {
         if (failed)//这个在加上锁的时候都会设置为false,我也不知道什么情况会failed,一般情况下failed都为false
           cancelAcquire(node);
        }
    }

至此,加锁逻辑基本结束,接下来看一下解锁逻辑,解锁相对容易些,就是唤醒头节点的下一个节点去尝试加锁,但是如果是非公平的唤醒头节点下一个节点尝试获取锁可能失败,毕竟其他线程可能直接获取到锁

public final boolean release(int arg) {
  if (tryRelease(arg)) {//去改变state的状态,改变成功后
     Node h = head;//获取头节点
     if (h != null && h.waitStatus != 0)//去头节点的下面一个节点进行唤醒
        unparkSuccessor(h);
        return true;
     }
     return false;
 }
-----------------------------------------------------------------------------------
protected final boolean tryRelease(int releases) {
  int c = getState() - releases;//首先对当前state操作,主要利用减法判断是否重入
  if (Thread.currentThread() != getExclusiveOwnerThread())//当前加锁线程和记录的获得线程不一致,抛出异常,返回free=false表示释放锁失败
     throw new IllegalMonitorStateException();
     boolean free = false;
   if (c == 0) {//state===0表示可以获取锁
        free = true;
        setExclusiveOwnerThread(null);
   }
     setState(c);//将state设置为0
     return free;//返回free
  }

加锁逻辑解锁逻辑结束,欢迎留言一起讨论,这个也是我学习自己进行注释的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值