这种东西就是心中有个大概就好了,尝试看看源码也听有意思的。 多几次debug就好了
注意:这个就是一个 state 在加一个双向链表
只要记住这个情况,后面debug起来就顺利了。
ReentrantLock lock = new ReentrantLock();
new Thread(()->{
lock.lock();
try {
System.out.println("thread 1 "+ new Date());
TimeUnit.SECONDS.sleep(2);
System.out.println(new Date());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
打上断点,看一下是怎么运行的。
public void lock() {
sync.lock();
}
sync是ReentrantLock内部自己实现的内部类,继承于AbstractQueuedSynchronizer(同步器)
同步器在 JUC 包中是很重要的,在同步器中,最主要的 state,和一个双向链表,这个链表中
有一个 head 和tail,链表中的节点都是存放的都是线程. 基本上JUC包中大多数的类都是继承于它。在 AbstractQueuedSynchronizer 中并没有 规定 state 的含义。具体的实现交给子类,不同的实现
就是不同的锁。
ReentrantLock中的sync有两个实现类。都是在ReentrantLock 维护。
/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
.......... 后面的就省略了.
看代码上面的注释就很清晰, Sync两种实现类,一种是公平,一种是非公平。 对了,Sync是abstract的。
对了 ReentrantLock 默认是非公平的。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync(); //默认是给了一个 非公平的同步器
}
在继续看lock
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//尝试把 state变为 1 底层还是通过unsafe的cas操作来做的
if (compareAndSetState(0, 1))
//将锁的持有者变为 当前时间
setExclusiveOwnerThread(Thread.currentThread());
else
//加锁失败,竞争失败, 下面的操作简单来讲就是 构建线程节点,将这个节点添加进 队列
acquire(1);
}
简单来讲,加锁就是改变 ReentrantLock 里面sync同步器的state, 1
表示加锁,0
表示未加锁。 在ReentrantLock 里面除了同步器之外,还有一个表示当前是哪个线程加锁了,
接下来就简单了,线程来了,就开始抢占state,谁能把state改为1,谁就占有这把锁。 抢不到的线程就在 同步器里面的线程队列里面等待,等state变为0了,按照加入的顺序,再次抢占锁。
可重入的锁
的原理 还是围绕 state来做文章,如果某个线程加锁成功,那此线程就是当前线程的所有者。这个线程再次加锁的时候,先看自己是不是锁的持有者,如果是就把state加1,state
是多少就表示他加了几次锁。解锁的时候就把state-1 ,如果state变为0了就把锁的持有线程变为null,表示还没有加锁。同时看队列中有没有线程节点,有的话就唤醒它。
看看 acquire(1) 方法
这个方法是 同步器(AbstractQueuedSynchronizer)里面的
public final void acquire(int arg) {
/*
再次尝试 获取锁
构建线程node ,添加进队列。
*/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
看看 tryAcquire 方法
注意 tryAcquire 是抽线方法,具体的实现得看子类,这里我选择的非公平的实现(NonFairSync)
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
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;
}
这里的就很简单了,得到当前线程,得到state,看state是否为0,如果为0尝试加锁,否则就看当前锁的持有者是不是自己,如果是就锁重入。
看看addWaiter方法
这个方法是 同步器(AbstractQueuedSynchronizer)里面的
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
根据上面的注释可以知道,这个方法里面就是创建了一个入队的节点。并指定这个节点的模式。
如果是第一个线程来的。pred肯定是null,那就会进入到enq()
方法。
看看enq方法
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize、
//初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
请注意:这里是一个死循环,意味着这里肯定是要干成事情的,要不然就不会返回,一直干下去。
线程第一个访问的时候,tail肯定是null,所以就先初始化。给head和tail造一个空对象,第一次循环结束,
第二次循环,tail就不是nul,这个时候头和尾两个位置都不是null,就会进到else里面,因为是双线链表,就有前驱和后继节点 node的前驱是t,t的后继是node,,t指向的tail节点,node 接在t的后面,最后把tail节点替换为 node 节点。
最后把 t节点返回, 到这里,tail已经是我们传进来的节点。head还是之前的new出来的空节点。返回的节点是之前的 tail节点,这个tail节点连接了传递进来的node节点。
那现在继续回到addWaiter
方法里面,
如果tail不是null,还是和之前的一样,连接节点,把tail设置为当前构建的节点。最后把这个节点返回,返回的时候是 构建的节点和它的前驱节点.
最后的返回就是 当前构建的节点和它的前驱节点。
acquireQueued() 方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//得到节点的前驱节点
final Node p = node.predecessor();
//如果是前驱节点是头节点,就尝试在获取一下
if (p == head && tryAcquire(arg)) {
//如果成功的话就将自己设置为头节点
setHead(node);
//释放头节点
p.next = null; // help GC
failed = false;
return interrupted;
}
//失败之后 是不是应该park呢?
if (shouldParkAfterFailedAcquire(p, node) &&
//pack住
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
注意 这里是一个死循环,死循环肯定是要做成某件事的,要不然就会一直尝试。
先看node 的前驱是不是头节点,如果是头节点,那就说明自己还能在尝试一下,万一在尝试的时候,突然获得锁了,那多好。将头节点设置为自己,断掉前驱节点,让gc回收,如果不断掉(久而久之就会有内存的消耗,然后炸掉)
这种就说到 引用的类型上面了,之后再说。
如果上一步没有成功,就来到了 shouldParkAfterFailedAcquire
方面里面了,看这个名字就知道是个啥了, 是不是应该park住当获取锁失败之后呢? 看看这个方法里面是个啥
shouldParkAfterFailedAcquire()方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
得到前驱节点的waitStatus,看是不是 是-1 ,是不是 >0 ,是不是=0
0 就会找到 第一个 waitStatus为 -1 的节点,
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
在这里就可以pak住当前的线程了。注意这里用的 park和unpack。
如果有线程释放了锁,就会unpark 链表中的线程。 线程就会继续从这里走,就还是从 acquireQueued 这个方法里面走。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
就会继续在这个for循环里面 尝试获取锁,获得到就运行,获取不到就会继续park.
public void unlock() {
sync.release(1);
}
调用还是同步器的 我这里默认的非公平的
public final boolean release(int arg) {
//尝试释放锁,主要就是将 state减-1 看是不是0 ,
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
首先是 tryRelease 这个方法里面主要就是将 state-1 看是不是0,如果是0 就表示释放干净了,将当前锁的拥有者变为null,如果不是0,就说明有锁重入发生。
如果释放成功了就会进if块里面,接下来就唤醒链表中的节点了。
主要是在 unparkSuccessor 方法里面
unparkSuccessor
private void unparkSuccessor(Node node) {
// 注意此时 node 为头节点
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
// 得到状态
int ws = node.waitStatus;
//状态 < 0
if (ws < 0)
//cas操作将-1 前一个节点的 state变为0
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//头节点的下一个节点
Node s = node.next;
//1
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;
}
//2
if (s != null)
//调用unpark方法
LockSupport.unpark(s.thread);
}
需要注意的是 前一个节点的 state为 -1 的时候 才能唤醒下一个节点
这里的流程就是先找到头节点,看头节点的statue是否为-1,只有为-1 的时候时候才可以唤醒他的后继节点。
先不看 编号为 1 的注释, 先看 第2 个注释。
第2个还是 就是调用unpark方法,唤醒这个线程。这个线程唤醒之后从 acquireQueued
这个方法中继续走,因为在 acquireQueued 方法中是死循环,线程就从这里开始继续走,又再一次的获取锁。
能获取到就 运行,获取不到就park住
在看看编号为1 的注释。
在这个注释里,看他上面的英文,线程会unpark他的后继节点,正常情况下唤醒的是下一个节点,特殊情况下,就从 链表的尾开始找,找到第一个 找到第一个 statue为 -1 的节点,找到他之后unpark。
锁重入怎么解锁
如果是锁重入的话 每一次调用unlock方法的时候就回吧state -1 如果 state变为0 才会唤醒后面的线程,如果是不为0 的话,就会返回false
protected final boolean tryRelease(int releases) {
//在unlock里面 调用的时候每一次 releases 都是1 state - 1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//看 c是不是0
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//设置 state ,这里不需要 cas操作,因为调用unlock方法的时候,肯定是持有锁的线程。
setState(c);
return free;
}
大体基本上就是这个样子了
在简单的说一下
ReentrantLock锁的实现是 依赖于一个同步器 AbstractQueuedSynchronizer,ReentrantLock里面自己维护了两个同步器,一个是公平实现,一个是非公平实现
锁 就是 一个state ,谁拿到锁了,就把找个状态变为 1 ,没有拿到锁的就在 AbstractQueuedSynchronizer 里面的线程队列里面等着。将自己变为这把锁的持有者
有谁释放了锁(也就是将state变为0 了) 就在AbstractQueuedSynchronizer 里面的线程队列里面唤醒一个。
在 ReentrantLock 中的实现是不通的 同步器,默认是非公平的
FairSync
NonfairSync
的实现
差别主要是在 释放锁之后抢锁的时候, 非公平的实现就是 如果一个线程刚刚释放完锁,这个时候来了一个新的线程,这个线程和线程队列里面的线程都会抢占锁
公平就是 新来的线程会别加在线程队列的后面。
打个断点 去看看
主要的方法有这个
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//看state
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;
}
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在tryAcquire 失败之后就直接添加队列了,不会 非公平的一样,上来就 cas 尝试修改。