锁,相信大家都听过也用过。如下伪代码:
Lock lk = new Lock();
try{
lk.lock();
// 不为人知的代码块…
…
} finally {
lk.unLock();
}
FAQ:
用起来简单丝滑有没有?但是请仔细思考一下如下几个问题:
-
什么场景下用它?
-
它帮我们解决了啥问题?
-
为啥区区一个锁就能解决这个问题,它为何这么diao,用了啥技术?
一般情况下只有在“并发”访问“共享”资源情况下,为了保证共享资源被安全的“读写”。往往我们就需要某种同步机制来保证“共享资源并发安全”,锁是同步机制的一种实现。
场景类比:

必要基础知识点
-
CAS
-
volatile
-
什么是自旋
-
AQS
-
如何实现对线程的阻塞和唤起
要彻底整明白JUC底层是如何实现锁的需要有如上基础知识的储备。(如果您上车还没来得及买票的,请下车后及时补票!!!O(∩_∩)O哈哈~)
源码分析
给大家分享一下源码分析的个人心得:(不仅限于本次JUC代码分析的场景)
看源码首先得有个大的思想:就是理解什么是程序?
个人理解:程序 = 数据结构 + 算法
在源码阅读的场景下,程序即代码,我更倾向于把:数据结构 + 算法,翻译成:模型+流程。 即:如何封装类模型,通过模型构成一个个“数据结构”,通过一些流程控制代码比如,if,for,return等等来实现“算法”。
最终源码阅读就分解为了:把代码中关键的模型类理清楚,然后有哪些算法在控制它们的读写。把“模型”和流程通过流程图的方式呈现出来。
主角ReentrantLock
ReentrantLock是基于AQS实现的一种可重入锁,它有公平和非公平锁的两种实现。
JUC的基石AQS
AQS即AbstractQueuedSynchronizer的缩写,这个是个内部实现了两个队列的抽象类,分别是
同步队列和
条件队列。其中
同步队列是一个双向链表,里面储存的是处于等待状态的线程,正在排队等待唤醒去获取锁,而
条件队列是一个单向链表,里面储存的也是处于等待状态的线程,只不过这些线程唤醒的结果是加入到了同步队列的队尾,AQS所做的就是管理这两个队列里面线程之间的
等待状态-唤醒的工作。
AbstractQueuedSynchronizer
其实是一个很重要的“模型”类,里面有非常非常非常重要的“数据结构”定义。内部类:Node尤为重要;
如下AQS一些关键的属性,及内部类,理解它们的含义及使用场景很重。


Thread
有一个隐性的点,很容易让人忽略。那就是线程:线程是程序执行的载体,它贯彻于程序允许的整个生命周期。
概览

代码性质数据结构


逻辑性数据结构
-
CLH 线程同步队列 (带有头、尾指针的双向链表,其节点就是 AQS 的内部类:Node的实例)

-
等待队列(单向链表,其节点也是 AQS 的内部类:Node的实例)
它不是今天的主角,不是本文内容的要点,这里只是把它的存在展示给大家。

算法=流程
在我看来理解就是代码的逻辑控制,各个数据通过什么样的流程构建数据结构,并在不同的流程中流转;
说白了:根据代码把流程图整清楚就达到理解“算法”的目的了。
如下图:整理了ReetrantLock的加锁和解锁流程

ReentrantLock实现原理
由于篇幅问题:这里只介绍它的非公平锁的流程
它与AQS的关系如下图所示:
AbstractQueuedSynchronizer、Sync、NonfairSync、FairSync 它们之间是模板模式的一种应用实现。在选择公平和非公平锁的逻辑可以看成是策略模式的变种。
ReentrantLock锁结构:

首先ReentrantLock继承自父类Lock,然后有3个内部类,其中Sync内部类继承自AQS,另外的两个内部类继承自Sync,这两个类分别是用来
公平锁和非公平锁的。 通过Sync重写的方法tryAcquire、tryRelease可以知道,
ReentrantLock
实现的是
AQS
的独占模式,也就是独占锁,这个锁是悲观锁。
ReentrantLock有个重要的成员变量:
private final Sync sync;
这个变量是用来指向Sync的子类的,也就是FairSync或者NonfairSync,这个也就是多态的
父类引用指向子类,具体Sycn指向哪个子类,看构造方法:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock有两个构造方法,无参构造方法默认是创建
非公平锁,而传入true为参数的构造方法创建的是
公平锁。
非公平锁的实现原理
当我们使用无参构造方法构造的时候即ReentrantLock lock = new ReentrantLock(),创建的就是非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
//或者传入false参数 创建的也是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
lock方法获取锁
-
lock方法调用CAS方法设置state的值,如果state等于期望值0(代表锁没有被占用),那么就将state更新为1(代表该线程获取锁成功),然后执行setExclusiveOwnerThread方法直接将该线程设置成锁的所有者。如果CAS设置state的值失败,即state不等于0,代表锁正在被占领着,则执行acquire(1),即下面的步骤。
-
nonfairTryAcquire方法首先调用getState方法获取state的值,如果state的值为0(之前占领锁的线程刚好释放了锁),那么用CAS这是state的值,设置成功则将该线程设置成锁的所有者,并且返回true。如果state的值不为0,那就 调用 getExclusiveOwnerThread 方法查看占用锁的线程是不是自己,如果是的话那就直接将state + 1,然后返回true。如果state不为0且锁的所有者又不是自己,那就返回false, 然后线程会进入到同步队列中。

final void lock() {
//CAS操作设置state的值
if (compareAndSetState(0, 1))
//设置成功 直接将锁的所有者设置为当前线程 流程结束
setExclusiveOwnerThread(Thread.currentThread());
else
//设置失败 则进行后续的加入同步队列准备
acquire(1);
}
public final void acquire(int arg) {
//调用子类重写的tryAcquire方法 如果tryAcquire方法返回false 那么线程就会进入同步队列
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//子类重写的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
//调用nonfairTryAcquire方法
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果状态state=0,即在这段时间内 锁的所有者把锁释放了 那么这里state就为0
if (c == 0) {
//使用CAS操作设置state的值
if (compareAndSetState(0, acquires)) {
//操作成功 则将锁的所有者设置成当前线程 且返回true,也就是当前线程不会进入同步
//队列。
setExclusiveOwnerThread(current);
return true;
}
}
//如果状态state不等于0,也就是有线程正在占用锁,那么先检查一下这个线程是不是自己
else if (current == getExclusiveOwnerThread()) {
//如果线程就是自己了,那么直接将state+1,返回true,不需要再获取锁 因为锁就在自己
//身上了。
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果state不等于0,且锁的所有者又不是自己,那么线程就会进入到同步队列。
return false;
}
tryRelease锁的释放
-
判断当前线程是不是锁的所有者,如果是则进行步骤2,如果不是则抛出异常。
-
判断此次释放锁后state的值是否为0,如果是则代表 锁有没有重入,然后将锁的所有者设置成null且返回true,然后执行步骤3,如果不是则 代表锁发生了重入执行步骤4。
-
现在锁已经释放完,即state=0,唤醒同步队列中的后继节点进行锁的获取。
-
锁还没有释放完,即state!=0,不唤醒同步队列。

public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//子类重写的tryRelease方法,需要等锁的state=0,即tryRelease返回true的时候,才会去唤醒其
//它线程进行尝试获取锁。
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//状态的state减去releases
int c = getState() - releases;
//判断锁的所有者是不是该线程
if (Thread.currentThread() != getExclusiveOwnerThread())
//如果所的所有者不是该线程 则抛出异常 也就是锁释放的前提是线程拥有这个锁,
throw new IllegalMonitorStateException();
boolean free = false;
//如果该线程释放锁之后 状态state=0,即锁没有重入,那么直接将将锁的所有者设置成null
//并且返回true,即代表可以唤醒其他线程去获取锁了。如果该线程释放锁之后state不等于0,
//那么代表锁重入了,返回false,代表锁还未正在释放,不用去唤醒其他线程。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}