ReentrantLock是Lock的默认实现,在了解ReentrantLock之前,我觉得有必要先看一下AQS框架分析和condition,ReentrantLock继承自AQS,只不过子类重写了AQS的部分方法。
构造方法
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
默认的构造器创建一个NonfairSync对象,就是非公平锁,有参的构造可以指定使用公平锁还是非公平锁。可以看到不管是NonfairSync还是FairSync都继承了Sync类,Sync又继承了AQS类,它在AQS的基础上又进行了扩展,感觉使用了典型的模板方式模式。
NonfairSync
首先看一下非公平锁的获取和释放。
1.1 lock方法
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
0表示当前是自由状态,1表示当前已获取到锁,使用CAS把当前线程的状态由0设为1,并把当前线程设为独占模式。多个线程只能有一个线程CAS成功,其它失败的线程就进入acquire方法中,acquire在AQS类中,不在多说,但是acquire中的tryacquire方法是在Sync中被重写。
protected final boolean tryAcquire(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)) { 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是否为0,如果state是0,使用CAS把state设为1,并把当前线程设为独占模式,直接返回true,表示已经获取到锁。如果state不为0,检查一下当前线程如果是独占线程,更新自己的state,表示重入的次数。其他情况就是获取锁失败,直接返回false。
1.2 unlock方法
public void unlock() { sync.release(1); }
释放锁的代码就简单多了,只有一句代码,直接调用release方法,具体还是看AQS分析,但是在Sync中重写了tryRelease方法。
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
这里入参是1,如果当前线程不是独占线程,直接抛出异常。计算如果释放后的state如果为0,表示成功释放锁,把独占线程设为null,更新state,直接返回true。
FairSync
公平锁与非公平锁的区别在于公平锁在获取锁的时候直接执行acquire方法,不会检查state。但在acquire中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; }
这个方法和非公平锁中的nonfairtryacquire方法很相似,不同处在于当state是0的时候,调用hasQueuedPredecessors方法。
public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }通过判断当前线程是否在同步队列的头节点,返回是否有比当前线程等待更久的线程。后进来的线程总是进入同步队列等待,会导致效率降低。所以大多数情况下还是使用非公平锁。
总结
ReentrantLock是一个用代码控制的锁,相比与synchronized,它提供了更细的锁粒度,而且提供了可中断锁,超时锁。synchronized就不能中断正在等待锁的线程,这是相比synchronized的优点。但是使用ReentrantLock必须手动释放锁。在jdk 1.8中ConcurrentHashMap已经使用了synchronized,说明synchronized已经得到了极大的优化。除非有明确的需要,否则一般推荐使用synchronized。
AQS是并发包的核心,真正理解了AQS实现原理,在看ReentrantLock就简单多了。这里不得不说一下Doug lea大神深厚的功力,用简洁的代码写出了复杂的逻辑。在工作中我们还是要花些时间来提高自己的java基本功。