首先介绍下各个参数的含义;
Node节点的参数含义:
关于SIGNAL、CANCELLED、CONDITION、PROPAGATE四个状态,JDK源码的注释中同样有了详细的解读,再用一张表格总结一下:
接下来查看源码,以下所有备注都写在代码中,主要本人也没写过什么博客,文字功底比较差,大家见谅。
下面说到的源码都采用非公平锁来讲解:
首先说下lock()方法
1 final void lock() { 2 // 设置state状态为1 3 if (compareAndSetState(0, 1)) 4 // 设置成功则设置当前独占线程为自己 5 setExclusiveOwnerThread(Thread.currentThread()); 6 else 7 // 表示已有其他线程抢到锁了,需要下一步操作 8 acquire(1); 9 }
acquire()方法
1 public final void acquire(int arg) { 2 // tryAcquire 重新抢占锁,可能此时锁已被释放 3 // 若抢占锁失败,通过addWaiter方法加入的等待队列 4 // acquireQueued,在等待队列中尝试抢占锁 5 if (!tryAcquire(arg) && 6 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 7 // 若标志线程中断了,则调用中断线程操作 8 selfInterrupt(); 9 }
先说tryAcquire(arg)源码,因使用的是非公平锁,则从第5行的tryAcquire(arg)定位到nonfairTryAcquire方法:
1 final boolean nonfairTryAcquire(int acquires) { 2 final Thread current = Thread.currentThread(); 3 int c = getState(); 4 // 若c == 0,标志当前已无线程占用着锁,则尝试通过CAS更新state值,更新成功则抢占锁成功,设置当前线程为自己 5 if (c == 0) { 6 if (compareAndSetState(0, acquires)) { 7 setExclusiveOwnerThread(current); 8 return true; 9 } 10 } 11 // 若当前线程与已抢占锁的线程为同一线程,表示是重入锁,即同一个线程可以多次获取锁,此时需要将state+1 12 // 当unlock也需要将state-1,直到state=0时,其他线程才有机会获取锁 13 // 其中重入锁的次数最多是Integer.Max_value值,从下面代码中即可看出 14 else if (current == getExclusiveOwnerThread()) { 15 int nextc = c + acquires; 16 if (nextc < 0) // overflow 17 throw new Error("Maximum lock count exceeded"); 18 setState(nextc); 19 return true; 20 } 21 return false; 22 }
addWaiter方法:
1 // 将当前线程放入等待队列中去 2 private Node addWaiter(Node mode) { 3 Node node = new Node(Thread.currentThread(), mode); 4 // Try the fast path of enq; backup to full enq on failure 5 Node pred = tail; 6 // 若尾部节点tail不为空,则直接将自己的Node放在tail后面即可 7 if (pred != null) { 8 node.prev = pred; 9 if (compareAndSetTail(pred, node)) { 10 pred.next = node; 11 return node; 12 } 13 } 14 // 若尾部节点为空,或者放在tail后面失败了,则通过enq方法将Node放入等待队列中 15 enq(node); 16 return node; 17 }
enq(node)方法:
1 // 通过for循环实现自旋功能,一旦tail节点为空或者设置尾部节点失败,都会重试,保证将Node放入等待队列中 2 private Node enq(final Node node) { 3 for (;;) { 4 Node t = tail; 5 // 若尾部节点为空,则将空的head节点赋值给tail节点,下次循环就进入else处理 6 if (t == null) { // Must initialize 7 if (compareAndSetHead(new Node())) 8 tail = head; 9 } else { 10 // 将Node节点放正在tail节点的后面,并将tail节点的next节点设置成Node 11 node.prev = t; 12 if (compareAndSetTail(t, node)) { 13 t.next = node; 14 return t; 15 } 16 } 17 } 18 }
addWaiter处理完毕了,将已放入等待队列的Node作为参数传给acquireQueued方法处理:
1 // 在等待队列中再次尝试获取锁,失败了才阻塞 2 final boolean acquireQueued(final Node node, int arg) { 3 boolean failed = true; 4 try { 5 boolean interrupted = false; 6 for (;;) { 7 // 获取Node节点的上一个节点 8 final Node p = node.predecessor(); 9 // 若Node节点的上一个节点是head节点,则尝试获取锁 10 // setHead(node);获取锁成功,则将自己设置为head节点 11 // p.next = null;将之前的head节点设置为空,方便GC回收 12 // failed = false;已成功获取锁,因不需要finally中的取消抢占锁操作 13 if (p == head && tryAcquire(arg)) { 14 setHead(node); 15 p.next = null; // help GC 16 failed = false; 17 return interrupted; 18 } 19 // shouldParkAfterFailedAcquire 下面说明 20 // parkAndCheckInterrupt 阻塞当前线程 21 if (shouldParkAfterFailedAcquire(p, node) && 22 parkAndCheckInterrupt()) 23 interrupted = true; 24 } 25 } finally { 26 // 当failed为true时,表示当前线程在这里执行超时或者中断了,才会导致for循环执行结束 27 // 因为只有一个地方设置了返回值的操作,并在返回值前设置failed为false 28 if (failed) 29 cancelAcquire(node); 30 } 31 }
shouldParkAfterFailedAcquire方法:
4 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 5 int ws = pred.waitStatus; 6 if (ws == Node.SIGNAL) 7 // 表示当前节点通知了他的上个节点下次通知我,当前节点应被阻塞 8 return true; 9 if (ws > 0) { 10 // pred节点被取消了,则从pred节点之前的节点开始直到取到waitStatus的值<=0的节点,此时pred节点已被赋值为waitStatus<=0的节点 11 // 并且此时将取消的Node与其他Node依赖关系去除了,GC可以回收了 12 do { 13 node.prev = pred = pred.prev; 14 } while (pred.waitStatus > 0); 15 pred.next = node; 16 } else { 17 // 因为当前节点的上一个节点的waitStatus=-1时,当前节点才能被阻塞,因此将其他状态节点设置为-1 18 // 而即使因并发,CAS设置失败了,也不影响,下次循环进来还可以继续处理 19 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 20 } 21 return false; 22 }
parkAndCheckInterrupt方法比较简单:
1 // 调用LockSupport的park方法阻塞线程,并返回当前线程的阻塞状态 2 private final boolean parkAndCheckInterrupt() { 3 LockSupport.park(this); 4 return Thread.interrupted(); 5 }
以下举例都以在分析非公平锁:
一、 线程1(name=Thread1)调用lock()方法
state = 1
exclusiveOwnerThread = Thread1
二、 线程1不释放锁的情况下,线程2(name=Thread2)调用lock()方法
1 由于state=1,compareAndSetState更新失败
2 执行acquire(arg)
2.1 执行tryAcquire(arg),尝试再一次获取锁,失败
2.2 addWaiter(Node.EXCLUSIVE)添加到等待队列尾部,因此时head节点还没,会初始化一个空的head节点在线程2前面
head->node(线程2)
2.3 acquireQueued(node,arg)再等待队列中尝试获取锁 进入for循环
2.3.1 if (p == head && tryAcquire(arg))由于线程2的上一个节点是head,尝试获取锁,失败
2.3.2 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
2.3.2.1 shouldParkAfterFailedAcquire(p, node)由于head是空节点,则waitStatus=0,更新waitStatus=-1,返回false,重新循环
2.3.2.2 再次执行2.3.1,尝试获取锁,失败(在这一步有个细节,相当于自旋操作,waitStatus默认为0,在设置为-1后还会再尝试获取锁,因为阻塞线程 会引起上下文切换,影响性能,多做一次获取锁的操作能减少性能的损耗)
2.3.2.3 再一次来到shouldParkAfterFailedAcquire(p, node),此时由于waitStatus=-1,返回true
2.3.2.4 parkAndCheckInterrupt()阻塞当前线程,就不执行下面的返回中断状态的程序代码了,线程2就挂起了
2.4 selfInterrupt()这里其实不会执行,因为线程2已经阻塞了
三、线程1(name=Thread1)调用unlock()方法
1 tryRelease(arg)将state减1
2 unparkSuccessor(head)
2.1 若head.waitStatus<0,CAS设置waitStatus=0
2.2 将离head节点最近的一个waitStatus<=0且不为空的节点unpark,即不阻塞了 state = 0 exclusiveOwnerThread = null
四、由于线程1释放锁了,且将线程2设置为不阻塞了,下面看线程2的行为
1 回到(二)的2.3.2.4,线程2继续执行下去,返回中断状态 继续for循环
2 if (p == head && tryAcquire(arg)),此时能获取锁了,将state=1,exclusiveOwnerThread=Thread2 state = 1 exclusiveOwnerThread = Thread2
这样从一到四,就完成了线程1获取锁,线程2阻塞,线程1释放锁,线程2获取锁的整个步骤
整理几个结论:
1.waitStatus=-1的节点的next节点才会被阻塞;
2.已经被取消的节点会在acquireQueued(node,arg)步骤中被放弃,GC会去回收;
3.释放锁的线程所占用的节点会在下个线程获取锁的时候被释放掉;
4.线程只有在head节点的next节点才会去尝试获取锁,获取不到锁就会阻塞;
疑问:
1.当线程2获取锁之后,会将自己设置为head节点,并把线程1的节点设置为空,帮助GC回收线程1的Node, 也就意味着,线程1在释放锁之后不需要自己去清除自己的状态,会有其他线程会来做这一步操作, 而如果没有其他线程进来,我想在unparkSuccessor那一步中,将head节点的waitStatus状态设置为0, 是不是告诉让GC来回收,不知道我理解的对不对?
下面说下公平锁与非公平锁的区分:
公平锁的lock方法:
1 final void lock() { 2 acquire(1); 3 }
非公平锁的lock方法:
1 final void lock() { 2 if (compareAndSetState(0, 1)) 3 setExclusiveOwnerThread(Thread.currentThread()); 4 else 5 acquire(1); 6 }
公平锁在tryAcquire(arg)方法中多了一个判断条件
1 // 表示只有在head节点的next节点获取队列中只有head和tail节点才可以去尝试获取锁 2 // 意思就是按队列顺序来执行,此方法用于公平锁的实现 3 public final boolean hasQueuedPredecessors() { 4 // The correctness of this depends on head being initialized 5 // before tail and on head.next being accurate if the current 6 // thread is first in queue. 7 Node t = tail; // Read fields in reverse initialization order 8 Node h = head; 9 Node s; 10 return h != t && 11 ((s = h.next) == null || s.thread != Thread.currentThread()); 12 }
其实就是非公平锁会优先更新state状态,不会在等待队列中去获取锁,公平锁在尝试获取锁之前还要判断当前线程是否是在head节点的next节点位置。