![b23603ade23d865ed270cc858a3e3348.gif](https://i-blog.csdnimg.cn/blog_migrate/95e1acdffcadf43d3a8640b8b55acea0.gif)
点击上方蓝字关注我们吧~
![eded767e01281277764523c05c6cd503.png](https://i-blog.csdnimg.cn/blog_migrate/15f8d53744add6618470c90dd8d7fac6.png)
ReentrantLock原理
![b5af52652170b12276645866183d3c57.png](https://i-blog.csdnimg.cn/blog_migrate/546cacec3e8c986aaae3ca328beed354.png)
1、类的继承层次
如下为Concurrent包中与互斥锁(ReentrantLock)相关类的继承层次![5eb90de6810f8b4dcd471ce3913205b5.png](https://i-blog.csdnimg.cn/blog_migrate/111402048ac14939080e91ce5daf1cda.png)
public interface Lock{
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException;
void unlock();
Condition newCondition();
}
ReentrantLock本身没有代码逻辑,
核心实现在其内部类Sync中。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
}
![b5af52652170b12276645866183d3c57.png](https://i-blog.csdnimg.cn/blog_migrate/546cacec3e8c986aaae3ca328beed354.png)
2、锁的公平性和非公平性
Sync是一个抽象类,它有两个子类FairSync与NonFairSync,分别对应公平锁和非公平锁。从ReentrantLock的构造函数可以看出, 默认为非公平锁,可执行为公平锁! public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁就是
按照先来后到的顺序拿锁,非公平就是
直接插队到前面抢锁。不同于现实生活,这里
默认为非公平锁,其实是为了提高效率,减少线程切换!
![b5af52652170b12276645866183d3c57.png](https://i-blog.csdnimg.cn/blog_migrate/546cacec3e8c986aaae3ca328beed354.png)
3、锁实现的基本原理
Sync的父类为 Abstract Queued Synchronizer,简称为(AQS), 被称作队列同步器,这个类非常关键! ReentranLock锁具备Synchronized功能,即可以阻塞一个线程。为此它需要有以下几个功能:- 需要一个State变量,标记该锁的状态。对State的操作要确保线程安全,也就是会用到CAS。
- 需要记录当前是那个线程持有锁!
- 需要底层支持对一个线程进行阻塞或唤醒操作!
- 需要有一个队列维护所有阻塞的线程。这个队列也必须是线程安全的无锁队列,也需要用到CAS。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer{
/**
* The synchronization state.
*/
private volatile int state;
}
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable {
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
}
这里需要说明的是,s
tate不仅可以是0(无),1(有),为了支持锁的可重入性可以大于1,比如同一个线程调用了五次lock,state就会为5,然后调用了五次unlock,state就会减为0!
针对3,在
UnSate类中,提供了阻塞或唤醒线程的一对操作原语也就是park/upark。LockSupport对这一原语进行了简单封装!
public class LockSupport{
...
public static void park() {
UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
...
}
在当前线程中调用park(),该线程就会被阻塞;在另外一个线程中,调用unpark(Thread thread),即传入一个被阻塞的线程,就可以
精确唤醒被阻塞的线程!
针对4,AQS中利用双向链表和CAS实现了一个阻塞队列,如下所示:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer {
...
static final class Node {
volatile Node prev;
volatile Node next;
volatile Thread thread;//每个Node关联一个被阻塞的线程
}
private transient volatile Node head;
private transient volatile Node tail;
...
}
阻塞队列是整个AQS核心中的核心,head指向双向链表的头部,tail指向双向链表的尾部。入队就是把新的Node加到tail后面,然后对tail进行CAS操作;出队就是对head进行CAS操作,把Head向后移一个位置!
![d1353397cc3b3c67d251808bd89e4e53.png](https://i-blog.csdnimg.cn/blog_migrate/f749e2f32375ab8f9ea141f208a62a1e.png)
![b5af52652170b12276645866183d3c57.png](https://i-blog.csdnimg.cn/blog_migrate/546cacec3e8c986aaae3ca328beed354.png)
4、公平与非公平Lock的实现差异
非公平锁一上来就尝试修改state值,也就是抢锁,不考虑队列中有没有其他线程在排队,是非公平的!而公平锁不抢,而是通过acquire方法去拿锁。 static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
}
acquire是AQS的一个模板方法,如下所示:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire()再次尝试拿锁,被NonFairSync和FairSync分别实现!acquireQueued()目的是把线程放入阻塞队列,然后阻塞该线程!
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//无人持有锁,开始抢锁
if (compareAndSetState(0, acquires)) {
//拿锁成功,设置拥有者为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//当前线程已经拿到锁了,再次重入,直接累加state变量
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;
}
对于公平锁来说,仅仅是多了一行if判断,即!hasQueuedPredecessors(),就是
当c==0的时候,并且排在队列的第1个时,才去抢锁,否则继续排队,这才叫公平!
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;
}
![b5af52652170b12276645866183d3c57.png](https://i-blog.csdnimg.cn/blog_migrate/546cacec3e8c986aaae3ca328beed354.png)
5、阻塞队列与唤醒机制
下面进入锁的最关键部分,即acquireQueued(..)函数内部一探究竟! public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
先说addWaiter(..)函数,就是为当前线程生成一个Node,然后把Node放入双向链表的尾部。要注意的是,只是把Thread对象放入到一个队列中,线程并未阻塞。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
在addWaiter()函数把Thread对象加入阻塞队列之后的工作就要靠acquire Queued()函数完成。
线程一旦进入accquireQueued()就会被无限期阻塞,即使有其他线程调用interrupt()函数也不能将其唤醒,
除非有其他线程释放了锁,并且该线程拿到了锁,才会从accquireQueued(..)返回。
final boolean acquireQueued(final Node node, long arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//如果自己在队列头部,则尝试拿锁
if (p == head && tryAcquire(arg)) {
//拿锁成功,出队列,同时把node的thread变量置为NULL
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//自己调用park()阻塞自己
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
首先,acquireQueued函数有一个返回值,表示什么意思呢?虽然该函数不会中断响应,但是他
会记录被阻塞期间有没有其他线程向他发送中断信号,如果有,则该函数返回true,否则返回false。 阻塞发生在下面这个函数中:
private final boolean parkAndCheckInterrupt() {
//LockSupport 会响应中断!
LockSupport.park(this);
return Thread.interrupted();
}
LockSupport.park(this);函数返回只有两种情况,一种是
其他线程调用了LockSupport.unpark()另一种是
其他线程调用了当前线程的t.interrupt(),也就是说
LockSupport.park(this)会响应中断! 也正是
因为LockSupport.park()可能被中断唤醒,acquireQueued(..)函数才写了一个for死循环。唤醒之后,如果发现自己排在队列头部,就去拿锁;如果拿不到锁,则再次自己阻塞自己。不断重复此过程,直到拿到锁。 被唤醒之后,
通过Thread.interrupted()来判断是否被中断唤醒。如果是情况1,会返回false;如果是情况2,则返回true。
![b5af52652170b12276645866183d3c57.png](https://i-blog.csdnimg.cn/blog_migrate/546cacec3e8c986aaae3ca328beed354.png)
6、unLock实现分析
对于 unLock来说并不区分公平还是非公平。 public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//[1]、释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//[2]、唤醒队列中的后继者
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//只有所得拥有者才有资格调用unlock函数,否则抛出异常!
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//每调用1次,tryRelase state减1,
// 直至减到0,代表锁可以被释放
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
AQS private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
unlock的例子还是比较简单的。
![b5af52652170b12276645866183d3c57.png](https://i-blog.csdnimg.cn/blog_migrate/546cacec3e8c986aaae3ca328beed354.png)
7、lockInterruptibly实现分析
我们再来扩展一下 lock 不能被中断,l ockInterruptibly()可以被中断,原因是检测到被中断直接抛异常。 private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
//[!] 检测到被中断直接抛异常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
明白了accquireQueued(..)原理,此处就很简单了。当parkAndCheckInterrupt()返回true的时候,说明有其他线程发送中断信号,
直接抛出InterruptedException,跳出for循环,整个函数返回。
end
![d28814d4efbda2d8a26524b53aa1daf2.png](https://i-blog.csdnimg.cn/blog_migrate/6dc062fd69661d46e4861b932f8b0c12.png)
![7bba008eb8c972b1ca48cbd800168d5c.png](https://i-blog.csdnimg.cn/blog_migrate/ecf6b98af766d78e96c8ca89feaa923e.png)
![d01e5ac14976a9eae7cb210612f60b1a.png](https://i-blog.csdnimg.cn/blog_migrate/59c5d382ade8d1857cb26e8fc4bec22a.png)
![71f180f294a49e13e564be4bb669060b.png](https://i-blog.csdnimg.cn/blog_migrate/7fac3e38513ff59d5a38a04c8444b071.png)
![33d441825b19a559e84797970f389902.png](https://i-blog.csdnimg.cn/blog_migrate/31f73fcd9a6d361226f03f78f9dffc22.png)