ReentrantLock内部有公平锁和非公平锁两种,而这两种锁都是基于AQS同步器实现的。AQS同步器太难看懂,先简单看下ReentrantLock的源码,再反推回去看AQS。
公平锁 VS 非公平锁:
1、公平锁能保证:老的线程排队使用锁,新线程仍然排队使用锁。
2、非公平锁保证:老的线程排队使用锁;但是无法保证新线程抢占已经在排队的线程的锁。
在ReentrantLock中,tryAcquire是公平锁和非公平锁实现的区别:
在公平锁中,每一次的tryAcquire都会检查CLH队列中是否仍有前驱的元素,如果仍然有那么继续等待,通过这种方式来保证先来先服务的原则。
而非公平锁,首先是检查并设置锁的状态,这种方式会出现即使队列中有等待的线程,但是新的线程仍然会与排队线程中的对头线程竞争(但是排队的线程是先来先服务的),所以新的线程可能会抢占已经在排队的线程的锁,这样就无法保证先来先服务,但是已经等待的线程们是仍然保证先来先服务。
非公平锁
默认构造函数:
默认就是使用的非公平锁(估计是因为非公平锁效率高吧)。
非公平锁加锁:lock()
- 首先使用CAS的方式将state设置为1,1代表当前线程获取了一次锁,并将当前线程设置为独占。也就是说ReentrantLock内部的非公平锁是一个独占锁。(其实从下面的tryAcquire()就可以看出来它是一个独占锁,AQS同步器独占锁抢占的命名就是这个)
- 如果状态设置未成功,则调用AQS类中的acquire(1)进行锁的强占(AQS中详细的怎么运行的后续文章分析吧)。acquire(1)会首先执行自定义的锁强占逻辑,不成功则执行由它定义的后续逻辑。记得内部会设置将强占的线程放到队列里面,这不就违反了公平的逻辑么?而且放到队里里面的话锁会自旋,有没有不自旋的方法?
看下它自定义的锁抢占方法:nonfairTryAcquire(int acquires)
我将它分成了上下两部分:
- 如果当前的state=0,表示锁被释放了,那么仍然通过CAS的方式将自己设置进去。
- 如果还是当前线程来强占锁,那么将state+1,这就是重入的逻辑,侧面也说明了AQS类没有实现重入逻辑。
释放锁:unlock(),只有一个方法,看来公平锁和非公平锁释放的逻辑是一致的
调用的是AQS类中的释放独占锁的逻辑,我们稍微看下AQS里面怎么实现的:
其实就是调用用户自身定义的tryRelease()逻辑,然后再做一些收尾工作。那么久看下自定义的释放锁的方法。
tryRelease(int releases):
其实核心就这句话,lock几次就要unlock几次,线程不在占有锁就是state减到0的时候。看到这里还不明白的一点是非公平怎么实现的,这个要看AQS了。
公平锁:
构造函数传入true:
看下它加锁的逻辑:lock()
和非公平锁是一样的,底层调用的还是AQS的逻辑,只是自定义的加锁的方式不一样,并且它也是实现的独占锁的接口,那就是说ReentrantLock中的锁都是独占锁。
和非公平锁唯一不同的是,它也有Queue的概念,抢占的线程是放入到队列中,每次从队列的头部获取线程,让这个线程来获取锁。至于重入的概念和非公平锁是一致。
ReentrantLock类中还有一个newCondition()方法,但是这个好像不是给它自己本身用的,而是给它对象用的:
可以参考我分析CyclicBarrier那篇文章,这个类中CyclicBarrier类中就是用来判断一组对象是不是达到了某种条件,额…后面看的越多估计会理解的越深一点。