ReentrantLock的原理解析
重入锁(ReentrantLock)是一种可重入无阻塞的同步机制。性能同synchronized接近(老版本jdk中性能很差)。
下面重点看下常用的lock()和unlock()方法的实现原理。
lock()
首先看下源代码:
public void lock() { sync.lock(); // 有公平同步和非公平同步两种机制 }
它的实现很简单,调用了一行sync的lock()方法,由于sync有两种实现方式:公平同步和非公平同步,默认是非公平同步,继续看代码:
final void lock() { if (compareAndSetState(0, 1)) // CAS机制,如果处于无锁状态,就直接锁定。lock锁中维护一个计数,大于0表示加锁了,值表示重入加锁次数 setExclusiveOwnerThread(Thread.currentThread()); // 设置锁被本线程持有,有什么用呢?用于可重入时检查使用 else acquire(1); // 如果锁引用计数不是0,说明已经上锁,检查是否可以重入
}
继续看"acquire(1)"的实现:
public final void acquire(int arg) { if (!tryAcquire(arg) && // 判断是否可以重入,即持有锁的线程是否是当前线程,如果是就把锁引用计数加1,返回lock成功 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 否则尝试把自己挂起 selfInterrupt(); } // 再次尝试加锁 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 锁引用计数值 if (c == 0) { // 为0,说明没有锁了 if (compareAndSetState(0, acquires)) { // 尝试加锁 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { // 持有锁的正是当前线程,那么就把锁引用计数加1,加锁成功 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
上面看到tryAcquire中会再次尝试加锁,或者如果持有锁的是当前线程,则把锁引用计数加1,返回加锁成功。
否则执行addWaiter,看下代码:
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; // 增加一个等待node带队尾 if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 前面使用CAS方式设置失败,则进入enq,循环使用CAS方式把Node加入队尾 enq(node); return node; } // 把node加入队尾 private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
// 加入队尾后,再执行acquireQueued(即acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
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); } }
上面的代码,重点看下这两个方法:
shouldParkAfterFailedAcquire
1 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 2 int ws = pred.waitStatus; // 第一次进来 ws是0,是初始值 3 if (ws == Node.SIGNAL) // 是否已经设置为等待唤醒状态,如果是,就可以挂起了 4 /* 5 * This node has already set status asking a release 6 * to signal it, so it can safely park. 7 */ 8 return true; 9 if (ws > 0) { 10 /* 11 * Predecessor was cancelled. Skip over predecessors and 12 * indicate retry. 13 */ 14 do { 15 node.prev = pred = pred.prev; 16 } while (pred.waitStatus > 0); 17 pred.next = node; 18 } else { // 设置自己的状态为Node.SIGNAL,但本次不允许挂起。下次再进来的时候,就可以返回true,并可以挂起了 19 /* 20 * waitStatus must be 0 or PROPAGATE. Indicate that we 21 * need a signal, but don't park yet. Caller will need to 22 * retry to make sure it cannot acquire before parking. 23 */ 24 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 25 } 26 return false; 27 }
parkAndCheckInterrupt
1 private final boolean parkAndCheckInterrupt() { 2 LockSupport.park(this); // 挂起自己 3 return Thread.interrupted(); 4 }
再看下park的实现:
1 public static void park(Object blocker) { 2 Thread t = Thread.currentThread(); 3 setBlocker(t, blocker); // 设置要挂起的线程,和挂起使用的对象 4 unsafe.park(false, 0L); // 挂起,jdk内置的挂起方法 5 setBlocker(t, null); // 唤醒后,取消挂起的对象 6 }
再看下setBlocker:
1 private static void setBlocker(Thread t, Object arg) { 2 // Even though volatile, hotspot doesn't need a write barrier here. 3 unsafe.putObject(t, parkBlockerOffset, arg); 4 } // 设置了阻塞线程和使用的阻塞对象
从上面代码可以看到,lock的主要流程有:
1. 检查锁引用计数,如果为0,表示可以锁定,就使用CAS方式把当前Thread对象设置为持有锁的对象,并把锁引用计数加1.
2. 检查锁引用计数,如果大于0,需要:
i. 检查当前持有锁的线程对象是否和本线程是同一个,如果是就对锁引用计数加1,加锁成功,这里体现了"可重入"特性。
ii. 否则,创建一个等待的node对象并加入到等待链接的队尾,然后调用系统的unsafe.park方法,把自己挂起。
Unlock
unlock就比较简单了,下面看下代码:
public void unlock() { sync.release(1); // 解锁 } public final boolean release(int arg) { if (tryRelease(arg)) { // 解锁 Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); // 解锁成功后,唤醒其他的等待线程 return true; } return false; }
这里面重点有两个地方,一个解锁,另外一个唤醒其他等待的线程。
先看解锁:
1 // 索引用计数减releases,实现了重入 2 protected final boolean tryRelease(int releases) { 3 int c = getState() - releases; 4 if (Thread.currentThread() != getExclusiveOwnerThread()) 5 throw new IllegalMonitorStateException(); 6 boolean free = false; 7 if (c == 0) { // 如果锁引用计数为0,说明是无锁状态了,需要把持有锁的线程变量置为空,并返回true 8 free = true; 9 setExclusiveOwnerThread(null); 10 } 11 setState(c); 12 return free; 13 }
再看唤醒其他线程代码:
1 // 当前面方法返回true,说明当前处于无锁状态了,这时候就可以唤醒其他的等待线程了 2 private void unparkSuccessor(Node node) { 3 /* 4 * If status is negative (i.e., possibly needing signal) try 5 * to clear in anticipation of signalling. It is OK if this 6 * fails or if status is changed by waiting thread. 7 */ 8 int ws = node.waitStatus; 9 if (ws < 0) 10 compareAndSetWaitStatus(node, ws, 0); 11 12 /* 13 * Thread to unpark is held in successor, which is normally 14 * just the next node. But if cancelled or apparently null, 15 * traverse backwards from tail to find the actual 16 * non-cancelled successor. 17 */ 18 Node s = node.next; 19 if (s == null || s.waitStatus > 0) { 20 s = null; 21 for (Node t = tail; t != null && t != node; t = t.prev) 22 if (t.waitStatus <= 0) // 找到等待的线程 23 s = t; 24 } 25 if (s != null) 26 LockSupport.unpark(s.thread); // 唤醒线程 27 }
相对来说,unlock就简单许多了,两步:1.解锁,减引用计数。2.唤醒其他线程。
posted on 2018-03-12 16:10 xinghebuluo 阅读(285) 评论(0) 编辑 收藏 举报