上一篇文章简单快速的过了下AQS的源码,梳理了下它提供的功能,以及它在并发的作用,今天看下ReentrantLock源码看看它的作用。
ReentrantLock基本属性
ReentrantLock(后面无特殊说明简称Lock)只有一个私有final属性Sync sync,Sync 是Lock中的抽象内部类,Sync类继承AQS并且在Lock中有两个实现NonfairSync、FairSync。Lock提供的方法基本都是调用sync这个属性的方法去真正执行,而NonfairSync、FairSync用来实现就是用来实现Lock的公平与非公平锁。
Sync实现方法
在分析这几个类之前先提前说明下,Lock把AQS中的state等于0表示没有线程持有锁。
Sync继承AQS,它自己实现了独有的一个方法nonfairTryAcquire(acquires),这个方法是先判断state是否为0,如果为0说明没有线程持有锁,会调用AQS的compareAndSetState(0,acquires)尝试设置state值,如果成功则把当前线程设置到exclusiveOwnerThread,表示当前线程获取到了锁,返回true表示成功。
如果state不等于0,会验证exclusiveOwnerThread是否是当前线程,如果是则会在当前state上加acquires,返回true表示成功。
如果最终都是则返回false。Sync的nonfairTryAcquire方法显示了Lock的可重入机制。
Sync实现AQS最关键的方法tryRelease(int releases)释放锁,会尝试在state上减releases,会验证当前线程是否等于exclusiveOwnerThread,也就是当前线程是否持有锁,如果不是会抛出IllegalMonitorStateException。如果设置后的state值为0则会把exclusiveOwnerThread为null,表示没有线程只有锁了,其他线程可以来获取。
NonfairSync实现方法
实现了一个方法lock(),主要流程是采用CAS方法把state的值从0设置为1,如果成功则把exclusiveOwnerThread设置为当前线程表示获取锁成功,如果失败则会调用AQS的acquire(1)实现获取成功或者阻塞线程。
然后实现了AQS的tryAcquire方法,方法内部调用的是上一节讲到的Sync中nonfairTryAcquire方法。
FairSync实现方法
FairSync也实现了lock方法,不过FairSync的lock方法是直接调用AQS的acquire(1)方法,没有先采用CAS方法直接设置。
同样也实现了tryAcquire方法,tryAcquire方法也是先判断state等于0,如果等于0会调用AQS的hasQueuedPredecessors方法判断AQS的链表中是否有数据,也就是要先判断锁中是否有阻塞线程,只有在没有阻塞线程的情况下才会调用AQS的compareAndSetState方法采用CAS方式设置state为对应的值。
公平锁与非公平锁的区别
从上面两个类来分析一下公平锁与非公平锁,他们的区别就在于lock、tryAcquire方法的实现,而它们对这个方法的实现区别在于非公平锁会直接先尝试获取锁,而公平锁会多一步阻塞队列是否有数据的验证。非公平锁在发现state为0的时候会直接尝试获取锁,而公平锁则会验证阻塞队列是否有阻塞线程,队列有会加到阻塞队列中。优势在哪里,下面我们举例说明下:
假如一个线程刚好释放锁后唤醒后一个等待的线程A准备去获取锁时,刚好一个新线程B进来尝试获取就会直接成功,如果在待唤醒的线程A获取锁之前这个新进来的线程B持有锁然后再释放锁就不会影响待唤醒线程A的获取锁,这样就提高了程序的吞吐量,即使线程A在获取锁时因为B还没释放而获取失败,也不过是重新获取,不论怎样都提高了线程B的响应,提高了整体索取锁的吞吐量。
非公平锁的优势如下图:
总之非公平锁通过充分利用释放锁到阻塞线程获取锁这段时间让新线程有可能直接获取到锁而避免了新线程阻塞而提高了获取锁的总体效率。
ReentrantLock主要功能实现
讲完了Lock三个静态了再来看它的主要方法就清晰了,因为它的方法大多是调用NonfairSync、FairSync的方法。
Lock方法无参构造函数里只做了一件事情就是new一个NonfairSync对应给sync属性,一个有参构造函数ReentrantLock(boolean fair)则可以选择NonfairSync、FairSync初始化一个对象给sync属性,所有Lock的默认是实现的非公平锁,毕竟效率高嘛。
ReentrantLock的lock()、tryLock()分别调用的是sync的lock、nonfairTryAcquire方法,
tryLock(long timeout, TimeUnit unit)方法则会直接调用sync的tryAcquireNanos方法也就是AQS中的方法。
unlock方法则是调用sync.release(1)把state减一。
主要就这几个方法了,ReentrantLock的每次加锁都是对state加1,可重入则是对state叠加,每次unlock都会对state减1,当减到0时表示线程释放锁。
总结
ReentrantLock中有三个AQS子类Sync、NonfairSync、FairSync,而NonfairSync、FairSync继承Sync。ReentrantLock只有一个属性Sync sync。所有方法都是调用sync的方法,而根据Sync的两个子类而实现公平与非公平锁,而公平锁与非公平锁的区别就在于非公平锁会直接尝试获取锁,而公平锁会在确认无阻塞线程下才会去尝试获取锁,否则就会加入到阻塞队列尾部。
Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!