敞开生长之旅!这是我参与「日新方案 12 月更文挑战」的第6天,点击查看活动概况
信任对并发有所了解的同学关于ReentrantLock
并不陌生。没错,今天的主角便是可重入锁ReentrantLock
非公正锁
ReentrantLock
的无参结构默认就会创立一个非公正锁
public ReentrantLock() {
sync = new NonfairSync();
}
咱们来看看他是怎么加锁的
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
- 首要是经过CAS设置state值为1;
- 哪个线程设置成功了,就将此线程设置成独占锁线程,也便是此线程取得锁;
- 不然履行acquire(1)。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
咱们先剖析tryAcquire(arg)
和acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
子办法,最终在回过头来剖析acquire(int arg)
办法
先看tryAcquire(arg)
办法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
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
的值,判别是否为0,也便是判别是否有线程占用锁; - 假如没有线程占用锁,当时线程经过CAS测验设置
state
值(测验获取锁),假如成功回来true; - 假如当时线程和独占锁的线程是同一个线程,则将
state
添加acquires
(可重入),并回来true; - 不然回来false。
总结下tryAcquire(arg)
办法的效果:测验获取锁,获取到了锁回来true,不然回来false。
addWaiter(Node.EXCLUSIVE)
办法首要是做了一个线程入队的操作,这儿就不细讲;
首要来看看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
这个办法。
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);
}
}
能够看到这个办法的主体内容便是一个死循环。
- 首要获取当时线程的node节点的上个节点
- 上个节点假如是头节点,测验获取锁;
- 假如获取到锁,设置当时节点为头节点,并将之前的头节点出队;
- 假如上述判别有一个为false,履行
shouldParkAfterFailedAcquire(p, node)
办法 - 假如
shouldParkAfterFailedAcquire
办法回来true,则调用park
办法阻塞当时线程; - 假如回来false,则持续循环。
来看看shouldParkAfterFailedAcquire(p, node)
究竟做了什么
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,回来true;
- 假如>0,则死循环去找到上上个节点(直到
waitStatus
<=0为止),回来false; - 不然将上个节点的
waitStatus
设置为-1,回来false。
实际上,shouldParkAfterFailedAcquire(p, node)
办法便是判别是否需求阻塞当时线程,并修改上个节点的waitStatus
的值为-1,表明上个节点的线程处于休眠状况。
最终,咱们来看看真实阻塞的办法parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
便是调用park
办法进行线程的阻塞,也便是说线程履行到这就会开释CPU,停在了这,等候被唤醒。
公正锁
公正锁绝大当地跟非公正锁的实现没啥区别,无非是在CAS获取锁之前判别了一下等候行列中的线程是否轮到了当时线程(hasQueuedPredecessors()
办法)
总结
到此,ReentrantLock
加锁过程就聊完了,这儿简略做个总结。
- 首要经过CAS设置
state= 1
成功便是获取到锁; - 失利则再次测验获取锁;
- 假如仍是失利,则将这个线程节点加入等候行列;
- 假如这个节点的上个节点是头节点会再次测验获取锁,假如成功即获取锁,并进行节点链表的新旧头节点的收支行列操作;
- 假如失利则会调用park进行阻塞,等候被唤醒。
文中如有不足之处,欢迎纠正!一同沟通,一同学习,一同生长 ^v^