从ReentrantLock到AQS源码
大家都知道在JDK1.6之前synchronized这个重量级锁器性能一直都是较为低下,虽然在1.6之后进行了大量的锁的优化策略,但是使用起来没有Lock灵活,例如获取锁与释放锁的可操作性,可中断性,超时获取锁等;在性能方面synchronized优化之后基本上和Lock扯平;在1.8之后ConcurrentHashMap用CAS和Synchronized取代了ReentrantLock,从1.8之后对ConcurrentHashMap的优化就可以看的出来对synchronized还是很重视的,毕竟是亲儿子;话不多说开始我们的源码之旅:
在源码之前我们先了解一下ReentrantLock和AQS(AbstractQueuedSynchronizer)的关系!
我们可以看到JUC包下的工具基本上都是基于AQS来实现的;,例如:ReentrantLock, CountDownLatch, CyclicBarrier, Semaphore等。而这些类的底层实现都依赖于AbstractQueuedSynchronizer这个类!可想而知这个AQS是多么的重要!
ReentrantLock实现了一个内部类Sync,该内部类继承了AbstractQueuedSynchronizer,所有锁机制的实现都是依赖于Sync内部类,也可以说ReentrantLock的实现就是依赖于AbstractQueuedSynchronizer类
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
//....
}
复制代码
我们点到为止,大致知道这个情况就可以,方便后面的理解;
一般我们使用ReentrantLock时如下:
private Lock lock = new ReentrantLock();
public void method() {
try {
lock.lock(); //获得锁
System.out.println("执行一些操作。。。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();//释放锁
}
}
复制代码
这里需要注意的是在初始化ReentrantLock的时候在构造方法中什么都没有传,使用的是默认的构造方法!也就是非公平锁!当我们传入ture时实例化的是公平锁;
//非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
复制代码
公平锁与非公平锁的区别
公平锁
下面我们先看看在源码中公平锁和非公平锁实现区别!接下来我们继续跟踪源码,我们先看公平锁的实现:
final void lock() {
acquire(1);
}
复制代码
上面调用lock.lock()
方法之后就进入了AQS(AbstractQueuedSynchronizer)
类中的acquire(1)
方法!并将1传入进去;我们看看acquire(1)
方法中做了什么?到这里就来到了AQS中!
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
我们看到首先是调用了tryAcquire()
方法!这句话就是尝试去加锁!我们这里先不关心acquireQueued()
方法!我们点进去tryAcquire()
方法查看发现这个方法AQS自身是没有实现的!而是需要子类自己去实现,这就能看的出来AQS使用的是模板设计模式!点进去只有我们就来到了ReentrantLock自己实现的tryAcquire()
方法中:记住我们先看的是公平锁(FairSync)的实现:
protected final boolean tryAcquire(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;
}
}
复制代码
- 首先是获取当前线程,拿到state这个用volatile修饰的int变量,这个变量的目的很简单,0表示没任何线程持有锁,大于0说明有线程持有锁!这里判断是否等于0,假设这时只有一个线程T1过来尝试加锁,这时state就是等于0的,因为目前为止没有任何地方给state赋值(int类型默认是初始是0);所以这个判断会进去,进到这个方法里面之后首先执行了
hasQueuedPredecessors()
方法,这个方法就是判断自己是否需要排队!记住这个方法,这也是和非公平锁的一个区别之处!
这里我们看下这个方法的实现:
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
复制代码
这个方法是AQS实现的,讲到这里我们第一次看到Node,我们还没有给Node赋值!所以到这里无论是tail还是head都是null;所以判断h!=t
这个是返回是false;这个hasQueuedPredecessors()
方法返回是false那上面的!hasQueuedPredecessors()
方法返回就是true;那就紧接着执行了compareAndSetState(0, acquires)
方法!对,你猜的没有错这个就是CAS操作!将当前T1的线程的状态从0设置为1,表示T1线程加锁成功!到这里我们先不向下分析了,我们这里的重点是放在公平锁和非公平锁的区别上,上面我们讲解了公平锁的实现,其实非公平锁和公平锁的实现差不多,我们来看下:
非公平锁
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
复制代码
如果是非公平锁再调用lock.lock()
方法的时候,我们在这里就看到了与公平锁的区别之处,这里上来就是调用CAS操作将自己的状态设置为持有锁!如果成功那就加锁成功,如果失败就是同样调用acquire(1)
方法!如果抢占锁成功就没有acquire(1)
方法啥事了,但是我们还是得去看一下acquire(1)
方法里面的实现!
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
tryAcquire(arg)
方法同样需要子类去实现,那我们就看看ReentrantLock这个子类怎么实现的非公平锁,
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
复制代码
在非公平锁里面的tryAcquire()
方法中调用的是nonfairTryAcquire(acquires)
方法!废话不多说直接进去看看:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
复制代码
呦!似曾相识不;和公平锁的实现上大同小异!同的我们就不看了!我们看看异在哪里?
- 同样上来就是获取当前线程,然后获取state的状态值;我们同样假设只有T1线程,这里state的值为0;然后直接执行了
compareAndSetState(0, acquires)
方法!还记得上面这块公平锁的实现吗?公平锁的实现多了一步操作就是看自己是否需要去队列中排队;但是非公平锁不用去判断!非公平锁不会去关心队列中有多少在排队的;自己先尝试去加锁;如果加锁成功就设置当前持有锁的线程为自己!如果CAS失败这个nonfairTryAcquire()
就会返回false!然后再去执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
,将自己添加到队列中的操作!到这里我们就看出来公平锁与非公平锁的区别了!
重入锁实现原理
我们知道synchronized
和ReentrantLock
都是可以重入的,都是属于重入锁!那重入锁怎么实现的呢?我们就以ReentrantLock
为例看看(因为我们看不到synchronized
的源码!)
我们上面介绍公平锁与非公平锁其实已经贴出了重入锁实现的代码:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
复制代码
重入锁的实现就是在else if 里面;上面我们假设只有T1一个线程过来,这里我们还是假设只有T1线程但是T1线程就不止有一个了,例如下面:
public static void main(String[] args) {
Lock lock = new ReentrantLock(true);
for (int i = 0; i < 3; i++) {
lock.lock();
System.out.println("执行一些操作。。。。。" + i);
}
for (int i = 0; i < 3; i++) {
lock.unlock();
}
}
复制代码
这个时候代码就会进行执行的流程是:
- 当第一个T1过来时,state等于0,CAS加锁成功!
- 当第二个T1过来时,因为第一个T1还没有释放锁,还没有执行
unlock()
方法!所以这个时候getState()
方法返回的不是0而是1,所以就会进入到else if
中进行判断;因为都是T1线程current == getExclusiveOwnerThread()
执行结果为true;然后将state的值加1; - 以此类推,当第三个T1进来的时候和第二个T1流程是一样的;
总结
用流程图解释一下上面讲述的公平锁,非公平锁,重入锁的整体流程:
这只是AQS的冰山一角;后面我们还没有继续深入讲解ReentrantLock
的解锁过程,AQS的独占锁的源码实现,共享锁的源码实现,可响应中断,不可响应中断的区别;