4、ReentrantLock底层是怎么实现的?
ReentrantLock继承体系
首先来看一下继承体系:
从上图我们可以看到,ReetrantLock本身只是实现了Lock接口,但是如何去实现一把完整的锁相应的功能呢,其实底层是借助于Sync这个静态内部类的对象来实现的。
这个Sync是一个同步器,有两种不同的实现
- FairSync 公平锁的同步器
- NonfairSync 非公平锁的同步器
ReentrantLock可以是一把公平锁,实现先来后到的抢锁机制。也可以非公平锁,允许锁释放的时候,刚来的线程去抢一抢锁,这样就可以避免阻塞和唤醒的开销,性能更高,但是容易出现饥饿现象,之前排队的线程可能由于新来的线程抢锁而一直拿不到锁。
AQS原理
那么同步器是如何实现的呢?它的底层基于一种叫AQS的队列同步器来完成的。
在Java并发包也就是JUC的包中,AQS实现了很多种线程安全的容器以及锁,具体如下:
AQS 队列内部维护的是一个 FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任
意一个节点开始很方便的访问前驱和后继。每个 Node 其实是由线程封装,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去;当获取锁的线程释放锁以
后,会从队列中唤醒一个阻塞的节点(线程)。
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled. */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking. */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition. */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate.
*/
static final int PROPAGATE = -3;
volatile int waitStatus;
//上一个节点
volatile Node prev;
//下一个节点
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
这些节点中保存的就是目前AQS中并发去抢锁的线程,有抢到的,也有没有抢到阻塞的,他们最终抢锁的结果会决定state(图中是status)状态的值,在reentrantlock中,> 0 代表有线程持有锁并记录重入的次数,0代表无人持有锁。
每一个子类都必须重写如下方法:
//独占式获取同步状态,实现该方法须查询并判断当前状态是否符合预期,然后再进行CAS设置状态。
protected boolean tryAcquire (int arg)
//独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态。
protected boolean tryRelease (int arg)
//共享式获取同步状态,返回大于0的值表示获取成功,反之获取失败。
protected int tryAcquireShared (int arg)
//共享式释放同步状态。
protected boolean tryReleaseShared (int arg)
//当前同步器是否再独占模式下被线程占用,一般用来表示是否被当前线程独占。
protected boolean isHeldExclusively ()
非公平锁的实现
我们以非公平锁为例看下具体的实现:
有两个小细节:
1、如果当前持有锁的线程就是本线程,会产生重入,state值会加1,所以state记录了重入的次数。
2、使用CAS的方式更新持有锁的线程引用,保证原子性,即便是在多线程情况下也能保证线程安全。
公平锁的实现
那么公平锁的实现有什么区别呢?
公平锁如果state == 0 情况下,会先判断队列中有没有线程,优先保证队列中的线程去处理,保证先来后到的公平性。
5、CAS是如何实现的?
什么是CAS?
CAS:Compare and Swap,即比较再交换。
CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS比较与交换的伪代码可以表示为:
do{ 备份旧数据; 基于旧数据构造新数据; }
while(!CAS( 内存地址,备份的旧数据,新数据 ))
CAS在CPU中有对应的指令来进行处理,JVM对CPU的指令进行了封装。如图中,CPU1想要将内存中的100值修改为101,此时CPU2也执行指令将100修改为102,同一时间只会有一个线程成功。图上为cpu1执行成功,内存中的值被修改为了101,而cpu2判断内存中的值已经不是100了,所以就造成了失败。
这种方式可以使用无锁的方式进行数据的修改,在JVM中大量使用了CAS,而并发包中所有Atomic为前缀的类,都是使用了CAS机制来保证原子性操作的类。