目录
尝试加锁
如果加锁不成功
重点
尝试加锁
最外层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 orderNode 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 failureNode 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 initializeif (compareAndSetHead(new Node())) // 这里面就是把head指向新建的node// tail指向新建的nodetail = 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 GCfailed = 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