ReentrantLock总结

简述 【ReentrantLock总结】ReentrantLock是java中非常重要的一个并发工具,相比于java原生的synchronized有着更好的性能

## 概念速查 ReentrantLock涉及的名称和概念较多,这里做一个简单的归类和解释,具体更为详细的内容,请自行Baidu或Google,这部分用于在阅读文章的时候,快速了解一些名称的概念,如果已经熟悉,请跳过。

快速预览 更强大的功能,玩玩意味着更为复杂的使用,ReentrankLock的使用比起synchronize,多了一个主动释放锁的代码,一个典型的使用示例如下
ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // ... method body }finally { lock.unlock(); }

注意unlock的操作一定要置于finally块中,这样才能保证锁一定能释放。
uml图 看完了简单的使用示例,我们来快速的看一遍ReentrankLock的结构,下面是用idea的工具快速生成的uml图,感谢idea,大大提高了我们的工作质量。

ReentrantLock总结
文章图片
ReentrantLock-UML.png 一次性过于深入的讨论,往往会迷失在繁琐的细节中,而难以把握全貌,而细节往往是由全局的目标决定的,所以我们一层一层的谈,不一次性深入最终代码。
由uml图,我们可以看出,ReentrantLock类是一个Lock接口的具体实现,每个ReentrantLock的实例,都持有一个sync对象,且这个sync是final修饰的,这个sync有两种具体的子类,分别是NonfairSync和FairSync,也就是非公平锁和公平锁。
ReentrantLock有两个构造方法,我们可以先看这两个方法,
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = (fair)? new FairSync() : new NonfairSync(); }

可以看出,所谓构造函数,其实就是初始化需要使用的sync的类型,默认是非公平锁。参考公平锁与非公平锁
简要的预备知识 CAS
ReentrantLock的核心是对CAS方法的使用,现代的CPU提供了特殊的指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet()使用了这些命令,具体请查阅CAS原理分析
wait
第一层解析 加锁部分
ReentrantLock的lock其实就是调用具体的sync的lock方法。
公平锁和非公平锁的加锁是有所不同的
对于公平锁来说如下
final void lock() { acquire(1); }

对于非公平锁来说,是如下的
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); elseacquire(1); }

很显然,我们可以看出对于公平锁来说,非公平锁多了一个操作。我们首先来解释一下compareAndSetState方法,这个方法来自于sync继承的AbstractQueuedSynchronizer父类。state为0表示当前锁没有被占用,如果大于0,则表示被持有了,使用compareAndSetState,底层是个CAS方法,如果锁没有被占用,则置为占用状态,并且通过setExclusiveOwnerThread,将当前线程设置为该锁的持有者。
tryAcquire这个方法的作用,java的官方注释上如下写到
Acquires in exclusive mode, ignoring interrupts
大意是"尝试在独占模式下获取,忽略中断"。
截止到目前为止,我们看到的加锁过程的流程图如下,很简单
ReentrantLock总结
文章图片
ReentrantLock第一层.png acquire的工作 接下来,我们研究一下acquire做的工作
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }

tryAcquire,尝试将锁的状态置为arg,如果成功,则结束function,如果失败,在CHL队列中添加一个新的独占锁的节点,节点如果添加失败,则不做后续处理,如果成功则使用selfInterrupt将当前线程中断。流程图如下
解锁部分
我们跳过acquire具体做了什么,我们直接来看unlock做了什么。
public void unlock() { sync.release(1); } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }

    推荐阅读