源码地址:https://github.com/nieandsun/concurrent-study.git
上篇文章《【并发编程】 — 从JVM源码的角度进一步去理解synchronized关键字的原理》讲到,在JDK1.6之前使用synchronized关键字会有这么一个问题:
无论同步方法是否正在面临并发,都会调用内核函数,也就是说都会让CPU进行用户态和内核态之间的切换 — 这种切换会带来大量的系统资源消耗。
本篇文章先来看一下Reentrantlock是如何做的。
Reentrantlock实现代码同步的本质
可以用下图进行表示:
其实大家可以想想,synchronized关键字实现同步方法的本质不也正是如此嘛!!!
理清了其本质之后,相信你自己都可以实现一把锁 —> 当然要想实现一个像Doug Lea搞得Reentrantlock这么牛X的锁,你还需要必备一些前置知识。
了解了synchronized关键字的底层原理之后,应该知道可以通过_recursions
变量是否为0判断monitor对象是否已经被抢占了,也就是锁对象是否被抢占了。 与此类似,Reentrantlock判断当前锁是否被抢占也使用了一个类似的变量 — state,在Reentrantlock中:
CAS的原理这里不再叙述,但必须清楚
有兴趣的可以参考我的文章《【并发编程】 — Compare And Swap(CAS)原理分析》对CAS操作进一步地了解。
当理清了让代码同步的本质之后,假如让你自己来实现一个锁的话,我猜你肯定会思考该怎样使未抢到锁的线程挂起的问题。
我猜你或许会相当如下方式:
当把所有已知的知识回顾了一遍后,你会发现好像并没有哪个技术可以真正很好的去实现线程的挂起与唤醒功能。。。
这时候就不得不再次提起《【并发编程】 — Compare And Swap(CAS)原理分析》那篇文章中提到的Unsafe类,它里面有两个方法 — park & unpark。
这两个方法的作用正是让指定的线程立刻挂起 和 从挂起状态中立刻被唤醒 — 多么适合的两个方法啊。
不知道你会不会有这种感觉,要是你也知道有这么两个方法,要是你能早生n多年,或许JUC包就是你开发的了。
之后的文章再着重讲解。
AQS(即AbstractQueuedSynchronizer类)中的数据结构如下:
private transient volatile Node head;//队首
private transient volatile Node tail;//队尾
private volatile int state;//锁状态,加锁成功则为1,重入+1 释放锁则为0
//静态内部类
static final class Node{} //存储当前线程、下一个节点、上一个节点、线程状态等的内部类
将Node节点进行简化后,可以用下图表示AQS的内部结构:
该数据结构保证了被挂起的线程可以按照一定的规则被唤醒 — 之后的文章肯定还会对此进行细究。
由上面两幅图可以看出,其实Reentrantlock中真正进行加锁和解锁的执行类是Sync ;而且因为Sync继承了AQS类,所以被挂起线程的链表数据结构及其维护都是由Sync来具体实现的。
这种实现方式其实达到了一个很重要的目的,即对Reentrantlock的使用者(即我们这些Java开发人员)屏蔽掉该类的具体实现细节,让我们可以只关注于具体的业务逻辑 —》
非常重要的一个思想!!!
其实读过CountDownLatch或Semaphore源码的可以知道,它们内部也使用了相同的设计 —> 即也都有一个继承了AQS的静态内部类Sync —>
一个非常标准的模版+策略模式!!!
了解了以上前置知识后,我们接下来看看Reentrantlock在同步方法交替执行时的具体处理逻辑。
主要看看Reentrantlock是如何在同步方法
未发生并发时不去调用内核方法
的具体原理。
非公平锁在同步方法交替执行、未出现并发时的处理逻辑大致如下:
由上图可以看出,当使用非公平锁时,多个线程交替执行同步方法,每个线程所要做的事就成了: (1)通过CAS把state的值变为1(当然还要把当前线程设置为锁拥有者,这里我就省略了) ---》 (2)执行同步方法 ---》 (3)将state的值再通过CAS改为0
(还省略了锁重入的过程,有兴趣的可以再深究一下,源码其实很简单)。
这整个过程根本不会涉及到内核函数的调用 —> 因此Reentrantlock的非公平锁非常完美的解决了本文开篇提到的问题!!!
公平锁在同步方法交替执行、未出现并发时的处理逻辑大致如下:
由上图可以看出,当使用公平锁时,多个线程交替执行同步方法,每个线程所要做的事就成了下面的样子:
在这个过程根本也不会涉及到内核函数的调用 —> 因此Reentrantlock的公平锁也非常完美的解决了本文开篇提到的问题!!!
end