目录
尝试加锁
最外层lock方法
ReentrantLock.class
public void lock() {
sync.lock();
}
进来发现是个抽象方法
abstract static class Sync extends AbstractQueuedSynchronizer
abstract void lock();
底下有两个实现类,一个实现的公平锁,另一个为非公平锁
我们看一下公平锁吧
static final class FairSync extends Sync
这里面可以看到入参为1
final void lock() {
acquire(1);
}
AbstractQueuedSynchronizer
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 这个是尝试去获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
我们先去看怎么去获取锁的
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
列一下实现类
cas我理解就是通过线程的状态值和主内存中的状态值进行比较,如果相同则修改,如果不同则修改失败
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread(); // 获取当前线程
int c = getState(); // 获取state的值
if (c == 0) { // 为0的话,说明还没有线程持有这把锁
// 因为你是公平锁,不能上去就去抢,要先去排队
if (!hasQueuedPredecessors() && // 判断是否需要排队
compareAndSetState(0, acquires)) { // 如果不需要排队就尝试通过cas获取锁
setExclusiveOwnerThread(current); //获取成功,那么该锁归这个线程所有
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 如果已经被加锁,但是还是这个线程想去获取锁,那么则进行+1操作,然后获取锁成功
// 如果进入到这个if,只能只有线程进来,不存在并发,不需要通过cas进行修改
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
看一下是否需要排队的方法
- 就是看头节点和尾节点是否相同,相同肯定就为空,还有些其他判断
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
如果加锁不成功
就需要尝试入队了,走 && 另一个方法了
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这边新建了一个互斥模式的节点,有互斥就会有共享模式,共享模式后面再说
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;
}
节点内比较重要的属性:
- waitStatus:这个是代表节点的生命状态,新建的节点这个值就为0
-
signal,cancelled,condition,propagate,0
-
- pre、next:这两个就是双向链表的前后指针
- thread:所有需要阻塞的线程肯定需要保存在某个位置等待唤醒
因为刚开始前后节点都为空,addwaiter方法里面第一个判断是不走的,走enq方法
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node())) // 这里面就是把head指向新建的node
// tail指向新建的node
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
这里面能看到,是个空循环,就是为了让拿不到锁的线程入队成功,开始走第一个逻辑就是为了初始化,设置一个空节点,反正这里面干了下图这个事,怎么干的我也不知道(我知道了,就是新建的节点作为head,tail = head,就是tail也是指向新建的节点)
看第二个判断就是初始化之后走这个判断,t就是新建的那个节点,初始化的节点,那个cas方法就是为了让tail指向node,最后把前驱节点进行返回,没什么用
接着回到这个方法,里面入参那是包裹着这个线程的节点以及1
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
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);
}
}
这里面可以看一下setHead方法,就是相当于将该节点置空变为前驱节点(就是之前初始化的空节点)
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
如果不是头节点或者没有获取锁成功,那么就走下面的这个逻辑
shouldParkAfterFailedAcquire
需要去获取前驱节点的waitStatus的状态,如果为0,就需要变为-1,变为一个可以被唤醒的状态
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;
}
然后被阻塞住
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
重点
acquireQueued(Node(currentThread), arg)
节点阻塞之前还得再尝试一次获取锁:
1,能够获取到,节点出队,并且把head往后挪一个节点,新的头结点就是当前节点
2、不能获取到,阻塞等待被唤醒
1.首先第1轮循环、修改head的状态,修改成sinal=-1标记处可以被唤醒.
2.第2轮循环,阻塞线程,并且需要判断线程是否是有中断信号唤醒的!
shouldParkAfterFailedAcquire(p, node)
waitestate = 0 - > -1 head节点为什么改到-1,因为持有锁的线程T0在释放锁的时候,得判断head节点的waitestate是否!=0,如果!=0成立,会再把waitstate = -1->0,要想唤醒排队的第一个线程T1,T1被唤醒再接着走循环,去抢锁,可能会再失败(在非公平锁场景下),此时可能有线程T3持有了锁!T1可能再次被阻塞,head的节点状态需要再一次经历两轮循环:waitState = 0 -> -1