ReentrantLock
一个简单的 Demo
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockTest { static ReentrantLock lock = new ReentrantLock(); static class T extends Thread { @Override public void run() { try { System.out.println(Thread.currentThread() + "开始尝试获取锁"); if (lock.tryLock(10, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread() + "成功获取锁"); TimeUnit.SECONDS.sleep(5); } } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(Thread.currentThread() + "开始释放锁"); lock.unlock(); } } } public static void main(String[] args) { T t1 = new T(); T t2 = new T(); T t3 = new T(); t1.start(); t2.start(); t3.start(); } }
加锁和解锁过程图解
加锁和解锁过程描述
- 在上面的程序中有三个线程同时去获取锁,同一个时刻只能又一个线程获取到锁,下面是进行入队去尝试获取锁的逻辑:
private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (nanosTimeout <= 0L) return false; final long deadline = System.nanoTime() + nanosTimeout; final Node node = addWaiter(Node.EXCLUSIVE); // 入队 boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { // 如果是头节点并且获取锁成功 setHead(node); p.next = null; // help GC failed = false; return true; } // 获取锁超过最大等待时间 nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) return false; // 获取锁失败进入阻塞 并且 并且超过自旋等待时间 if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) // 进入阻塞 nanosTimeout 为阻塞时间 LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
- 非公平锁尝试加锁的逻辑, 如果没有线程持有锁,那么就去通过 CAS 尝试加锁,如果是当前线程持有锁那么就 state + 1 累计,这里也可以看出
ReentrantLock
支持重入。
// 非公平锁的逻辑 // 如何理解插队, 这里的插队是当前队列中被唤醒的线程, 和当前加入的线程都可以被执行 // 如果当前加入线程比队列中唤醒的线程先获取到锁, 就是插队现象 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 无锁状态, 尝试竞争 if (c == 0) { if (compareAndSetState(0, acquires)) { //是否获取到锁 setExclusiveOwnerThread(current); return true; } } // 当前线程持有锁, state 计数 +1 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; }
- 如果获取到锁, 会将当前节点设置为头节点。并且返回 true 如果获取锁失败,并且超过获取锁的自旋时间那么当前线程将进入阻塞,阻塞是通过调用
LockSupport.parkNanos(this, nanosTimeout);
实现的。在这个过程中可能调用多次shouldParkAfterFailedAcquire
方法。
shouldParkAfterFailedAcquire
可以用来修改当前节点的状态,和对链表上无 效的节点出队
/** 当获取锁失败后, 检查更新新节点状态如果是需要阻塞返回, true * <p> * 一个前继节点 waitStatus = 0, 第一次将继续设置为 SIGNAL, 告诉当前线程准备进入阻塞, 此时依旧获取不到, 当前线程进入阻塞 * * @param pred 前继节点 * @param node 当前节点 * @return {@code true} if thread should block */ private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; // 前继节点的状态, 第一次进入的话, 一定是 0 if (ws == Node.SIGNAL) return true; if (ws > 0) { do { // 出队, 剔除无效的节点 node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 第一次进来, pred.waitStatus = 0 执行这个分支 // 将前继节点的状态修改为 SIGNAL, 表示 pred.next 节点需要被唤醒(此时准备进入阻塞, 但是还未被阻塞, 再次获取锁失败之后才会被阻塞) compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
- 解锁逻辑,下面是解锁的逻辑, 首先会进行解锁,如果 state 的状态修改为 0, 然后再去唤醒队列中排队的线程。
// 解锁 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; // 判断是否有需要唤醒的线程 if (h != null && h.waitStatus != 0) //waitStatus 的值为 0, 只有当后继存在节点才会被设置为该值不为 0, 此时需要唤醒后继线程 unparkSuccessor(h); return true; } return false; } // tryRelease protected final boolean tryRelease(int releases) { int c = getState() - releases; // 判断是否是当前线程持有锁 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // 如果 state == 0 表示当前线程不在占有该锁 free = true; setExclusiveOwnerThread(null); } setState(c); return free; } // 唤醒队列中的线程 private void unparkSuccessor(Node node) { // 将当前节点状态修改为 0 int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; 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); }
- 当前节点被唤醒逻辑,首先会在
shouldParkAfterFailedAcquire
方法中出队,然后尝试加锁如果加锁成功就返回 true.