zoukankan      html  css  js  c++  java
  • Java多线程——ReentrantLock源码阅读

    上一章《AQS源码阅读》讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲)。
    ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解说明

    ReentrantLock与隐式锁synchronized功能相同,但ReentrantLock更具有扩展性。
    《锁优化》里提到Java在1.6对隐式锁synchronized做了锁的优化,使其性能与显式锁性能相差无异。所以在两者的选择上,更多的是考虑用法,以及功能上的扩展。


    ReentrantLock是线程独占的,不能与其他线程共享。所谓的重入,就是当本线程想再次获得锁,不需要重新申请,它本身就已经锁了,即重入该锁。
    为什么会允许锁重入呢?因为该线程已经拥有锁了,不会受其他线程干扰,那么里面的共享变量就不会因为多线程执行造成线程不安全。相当于代码已经在串行执行了,没必要再申请多余的锁了,而是重入当前的锁。

    ReentrantLock会提供一个公平锁的模式,如果选择这个模式,会尽量使得获取锁是公平的,先来先得,但不一定严格按顺序。
    如果选择了公平锁,性能上会比不使用(默认)低一些。没有一定保证顺序,同时也降性能,所以如果没有特别的要求,尽量使用默认的非公平锁。
    现在基于以上的认识,来看看ReentrantLock的基本实现吧。

    ReentrantLock概览

    ReentrantLock是实现Lock接口的。所以主要的方法就是Lock接口定义的方法,包括lock()、tryLock()、unlock()、newCondition()等。
    lock()与tryLock()的区别就是前者会一直等到直到获取锁,后者则是尝试在当时获取锁,不会重复去申请获取。
    这个newCondition()感觉比较突兀,看起来完全不了解有什么用,和Lock有什么关系,我们后面再详细了解。

    ReentrantLock里面有一个最核心的成员变量,sync。sync的类型就是内部类Sync。它是AQS的子类,也就是说它就是实现ReentrantLock同步的工具。而FairSync和unFairSync则是Sync的子类,封装了是否公平的功能,用于赋值给sync成员变量。

    Sync同步器

    Sync是继承上文所介绍的AQS,是ReentrantLock里面的NonfairSync和FairSync的父类。
    看注解可以知道,Sync用了AQS的state(状态原子值)来标识锁被持有的数量。

    在AQS中,tryRelease()是没有定义的,所以在Sync中重写了。

    先判断下申请解锁的线程是否独占锁的线程,否则抛出异常退出。
    然计算新的state值,用当前state减去releases值。对于state值和releases值到底是多少,这里可以先留个悬念,但大家可以思考下上面注解的定义也可以大概猜出来。
    最后判断新state值是否为0,为0则没有线程占用,所以设当前独占线程为空,并且更新state。这里更新state值并不需要用CAS原子操作,因为只有一个线程会占用这个锁,不是这个线程都异常退出了。

    AQS中核心的tryAcquire()方法并没有在这里实现,因为子类NonfaiSync和FairSync的实现并不一样。但这里同样需要用到nonfairTryAcquire,所以抽象出来了。但为什么同样需要,暂时不得而知,带着问题后面再看看。
    先判断当前锁的state是否为0,为0则表示没人获取,然后通过CAS更新为acquires值(依然不知道值是多少),同时更新当前线程为锁的独占线程。
    如果state不为0,则表示有线程已经占有了。但可能占有的线程是当前线程,那么当前的state会加上acquires值。
    这里很容易就看出来state就是代表重入的次数!所以上面的谜题就解开了,releases,aquires都是代表每次申请的值,在ReentrantLock肯定都是1,他们的计算总值就是原子值state。
    如果state不为0,也不是被当前线程占用,那么返回false获取失败。

    NonfairSync

    没啥特别的,直接调用Sync的方法。也没做修改。

    FairSync

    公平锁的同步器。只有当递归调用或者没有其他等待者,再或者他自己本身排第一才能获取锁。
    这话比较绕口,大概意思应该是不停地轮询申请锁,直到自己排到队列的第一才能获取。

    乍看一看,这个方法基本和父类Sync的nonfairTryAcquire()一样,唯一不同点就是在没有线程占用的时候(state=0),多了个!hasQueuedPredecessors()前置判断。

    这个方法用来判断是否队列为空,或者当前线程是否在队列的最前面。
    所以公平锁模式下,想要能获取锁,除非自己排在队列的最前面。
    综上看,FairSync根本没有调用到nonfairTryAcquire(),为何说子类都需要用到呢?继续留着悬念,后面解答。

    @ReservedStackAccess

    可以看到上面介绍的tryAcquire()和tryRelease()都有@ReservedStackAccess。这个注解到底有什么用?
    查找了下资料,这个是JEP 270添加的新注解。它会保护被注解的方法,通过添加一些额外的空间,防止在多线程运行的时候出现栈溢出。具体看下图

    lock()、tryLock()成员函数

    ReentrantLock里面的lock()方法是调用成员变量sync的acquire()。
    无论是否公平锁都是直接调用AQS的acquire()方法,不过就是各自有tryAcuqire()的重写,即上文所说的内容。
    参数1,是透传给tryAcquire()的,所以这里代表是入锁一次的意思。

    而tryLock()调用的是成员变量sync的nonfairTryAcquire()。上文说到Sync内部类抽象了这个方法出来,说到子类都会用到,说的正是tryLock()方法需要用到。
    所以显而易见的,无论是否公平锁,调用tryLock()都是用的非公平锁的方法。为什么呢?
    因为tryLock()的try只是尝试,无论是否公平,对于方法来说没有必要,只是尝试申请的时候能否获取锁而已。

    至于其他成员函数,大都是围绕获取线程和队列的状态,没什么特别的,在这里不再赘述,有兴趣的可以看看源码。

    总结

    回顾下要点

    1. ReentrantLock是一个可重入的锁(被当前占用的线程重入)。
    2. 它有两种模式公平与非公平,通过NonfairSync和FairSync赋值sync成员变量实现。
    3. 两种模式都是AQS的子类,通过重写tryAcquire()区别不同。公平锁多了是否在队列的头的判断。
    4. tryLock()方法没有区分模式,都是一样的。

    上文提到的newCondition()还没有涉及到,等后续再起一章节说下这个Condition。


    更多技术文章、精彩干货,请关注
    博客:zackku.com
    微信公众号:Zack说码

  • 相关阅读:
    hdu 4521 小明系列问题——小明序列(线段树 or DP)
    hdu 1115 Lifting the Stone
    hdu 5476 Explore Track of Point(2015上海网络赛)
    Codeforces 527C Glass Carving
    hdu 4414 Finding crosses
    LA 5135 Mining Your Own Business
    uva 11324 The Largest Clique
    hdu 4288 Coder
    PowerShell随笔3 ---别名
    PowerShell随笔2---初始命令
  • 原文地址:https://www.cnblogs.com/zackku/p/9981844.html
Copyright © 2011-2022 走看看