释放锁。
// java.util.concurrent.locks.ReentrantLock.unlock() public void unlock() { sync.release(1); } // java.util.concurrent.locks.AbstractQueuedSynchronizer.release public final boolean release(int arg) { // 调用AQS实现类的tryRelease()方法释放锁 if (tryRelease(arg)) { Node h = head; // 如果头节点不为空,且等待状态不是0,就唤醒下一个节点 // 还记得waitStatus吗? // 在每个节点阻塞之前会把其上一个节点的等待状态设为SIGNAL(-1) // 所以,SIGNAL的准确理解应该是唤醒下一个等待的线程 if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } // java.util.concurrent.locks.ReentrantLock.Sync.tryRelease protected final boolean tryRelease(int releases) { int c = getState() - releases; // 如果当前线程不是占有着锁的线程,抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 如果状态变量的值为0了,说明完全释放了锁 // 这也就是为什么重入锁调用了多少次lock()就要调用多少次unlock()的原因 // 如果不这样做,会导致锁不会完全释放,别的线程永远无法获取到锁 if (c == 0) { free = true; // 清空占有线程 setExclusiveOwnerThread(null); } // 设置状态变量的值 setState(c); return free; } private void unparkSuccessor(Node node) { // 注意,这里的node是头节点 // 如果头节点的等待状态小于0,就把它设置为0 int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 头节点的下一个节点 Node s = node.next; // 如果下一个节点为空,或者其等待状态大于0(实际为已取消) 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; } // 如果下一个节点不为空,则唤醒它 if (s != null) LockSupport.unpark(s.thread); }释放锁的过程大致为:
(1)将state的值减1;
(2)如果state减到了0,说明已经完全释放锁了,唤醒下一个等待着的节点;
未完待续,下一章我们继续学习ReentrantLock中关于条件锁的部分
彩蛋为什么ReentrantLock默认采用的是非公平模式?
答:因为非公平模式效率比较高。
为什么非公平模式效率比较高?
答:因为非公平模式会在一开始就尝试两次获取锁,如果当时正好state的值为0,它就会成功获取到锁,少了排队导致的阻塞/唤醒过程,并且减少了线程频繁的切换带来的性能损耗。
非公平模式有什么弊端?
答:非公平模式有可能会导致一开始排队的线程一直获取不到锁,导致线程饿死。
推荐阅读死磕 java同步系列之AQS起篇
死磕 java同步系列之自己动手写一个锁Lock
死磕 java魔法类之Unsafe解析
死磕 java同步系列之JMM(Java Memory Model)
死磕 java同步系列之volatile解析
死磕 java同步系列之synchronized解析
欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。