一、类图结构
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的实现类,这就取决于使用构造方法创建的是公平锁还是非公平锁。
public void lock() {
if(compareAndSetState(0,1)) {
setExclusiveOwnerThread(Thread.currentThread())
}else {
acquire(1);
}
}
- 使用CAS算法,修改state的值为1,同时调用setExclusiveOwnerThread方法来设置当前线程为占用的线程。如果被占用了,那就调用acquire方法,传递参数为1,下面看一下这个方法的源码。
public final void acquire(int arg) {
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) {
if(compareAndSetState(0,acquires)) {
setExlusiveOwnerThread(current);
return true;
}else if(current == getExlusiveThread()){
int nextc = c + acquires;
if(nextc <0) {
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();
if(c == 0) {
if(!hasQueuedPredecessors() && compareAndSetState(0,acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if(current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if(nextc<0) {
throw new Error("Maximum lock count exceeded");
}
setState(nextc);
return true;
}
return false;
}
三、源码:
- 所在包:com.ruigege.ConcurrentListSouceCodeAnalysis5
https://github.com/ruigege66/ConcurrentJava
- 欢迎关注微信公众号:傅里叶变换,个人账号,仅用于技术交流
