zoukankan      html  css  js  c++  java
  • Java高并发25-独占锁ReentranLock的原理

    一、类图结构

    25.1
    25.1
    • ReentrantLock是一个可重入锁,只有一个线程可以获取到该锁,其他线程想要获取该锁的时候会被放到AQS队列中。
    • 从类图中可以看到实现了Lock接口,内含一个Sync类型变量,该类型是继承自AQS抽象类,同时又有两个继承了类,分别为公平锁和非公平锁。
     Sync sync;
     
     public ReentrantLock() {
      sync = new NonfairLock();
     }
     
     public ReentrantLock(boolean fair) {
      sync = fair? new FairLock():new NonfairLock();
     }
    • 这里的AQS中state变量代表可重入的次数,0为该锁为空闲阶段,1为该锁被某线程占用,当该线程再次重入的时候,该值就会递增;释放的时候,该值就会递减,直到递减为0,才表示该锁已完全释放,其他线程才有拿到的机会。

    二、获取锁

    • 从类中可以看到获取锁的方法是lock(),下面是实现
     public void lock() {
      sync.lock();
     }
    • 直接把锁交给了Sync的实现类,这就取决于使用构造方法创建的是公平锁还是非公平锁。
    • 下面看一下非公平锁lock的实现锁
     public void lock() {
      // CAS设置状态值
      if(compareAndSetState(0,1)) {
       setExclusiveOwnerThread(Thread.currentThread())
      }else {
       acquire(1);
      }
     }
    • 使用CAS算法,修改state的值为1,同时调用setExclusiveOwnerThread方法来设置当前线程为占用的线程。如果被占用了,那就调用acquire方法,传递参数为1,下面看一下这个方法的源码。
     public final void acquire(int arg) {
      // 调用ReentrantLock重写的tryAcquire方法
      if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE) ,arg)) {
       selfInterrupt();
      }
     }
    • 之前说过AQS并没有提供可用的tryAcquire方法,tryAcquire方法需要子类自定定制化,所以上述的代码会调用ReentrantLock重写的tryAquire方法。我们先看一下非公平锁的代码
     protected final boolean tryAcuqire(int acquires) {
      return nonfairTryAcquire(acquires);
     }
     
     final boolean nonfairTryAcquire(int acquires) {
      final Thread current = Thread.currentThread();
      int c = getState();
      
      if(c == 0) {
       // 当前AQS状态值为0,也就是没有线程获取到了该锁,那么我们让当前线程占用该锁
       if(compareAndSetState(0,acquires)) {
        setExlusiveOwnerThread(current);
        return true;
       }else if(current == getExlusiveThread()){
        // 该锁已经被当前线程占用,那么也就是重入的情况
        int nextc = c + acquires;
        if(nextc <0) { // 这种情况就是重入的次数太多了,超过了int正值的范畴
         throw new Error("Maxium lock count exceeded");
        }
        setState(nextc);
        return true;
       }else {
        return false;
       }
      }
     }
    }
    • 代码(4)会查看当前锁的状态值是否为0,为0则说明当前该锁空闲,那么就尝试CAS获取该锁,将AQS的状态值从0设置为1,并设置当前锁的持有者为当前线程,然后返回true,如果当前状态值不为0则说明该锁已经被某个线程所拥有,,所以代码(5)查看当前线程是否为该拥有者,如果当前线程是该锁的持有者,则状态值为1,然后返回true,这里需要注意的是,nextc<0说明可重入次数溢出了,如果当前该线程不是锁的持有者则返回false,然后其会放入到AQS阻塞队列。
    • 上面是非公平锁的实现代码,回过头来看看非公平锁体现在哪里。首先非公平是说尝试获取锁的线程并不一定比后尝试获取锁的线程优先获取锁,
    • 这里假设线程A调用了lock()方法执行到nonfairTryAcquire的代码(4),发现当前状态值不为0,所以执行代码(5),发现当前线程不是线程持有者,则执行代码(6)返回false,然后当前线程放入AQS阻塞队列。
    • 这时候线程B也调用了lock方法执行到nonfairTryAcquire的代码(4),发现当前状态值为0了(假设占有该锁的其他下称释放了该锁),所以通过CAS设置获取了该锁,明明是线程A先请求获取该锁,这就是非公平锁的体现,这里线程B在获取锁之前并没有查看当前AQS队列里面是否有比自己更早请求该锁的线程,而是使用了抢夺策略,那么下面看看公平锁是怎么实现公平的,公平锁的话只需要看FairSync重写的tryAcquire方法。

     protected final boolean tryAcuqire(int acquires) {
      final Thread current = Thread.currentThread();
      int c = getState();
      // (7)当前AQS状态值为0
      if(c == 0) {
       // (8)公平性策略
       if(!hasQueuedPredecessors() && compareAndSetState(0,acquires)) {
        setExclusiveOwnerThread(current);
        return true;
       }
      }
      
      // (9)当前线程是该锁持有者
      else if(current == getExclusiveOwnerThread()) {
       int nextc = c + acquires;
       if(nextc<0) {
        throw new Error("Maximum lock count exceeded");
       }
       setState(nextc);
       return true;
      }
      return false;
     }

    三、源码:

  • 相关阅读:
    Testdisk 操作指南(硬盘分区表恢复)
    ThinkPHP下使用Uploadify插件提示HTTP Error (302)错误的解决办法
    C#获取计算机CPU的温度
    C# 获取显示器的物理尺寸或分辨率
    获取windows 操作系统下的硬件或操作系统信息等
    AD CS relay attack
    内网密码收集[Foxmail]解密
    如果你是业务线的程序员
    浅析php curl_multi_*系列函数进行批量http请求
    memcached讲解
  • 原文地址:https://www.cnblogs.com/ruigege0000/p/14599606.html
Copyright © 2011-2022 走看看