ReentrantLock
例子
我们还是制造一个计数器的线程安全问题:
package reentrantlock;
public class TestMySynchronizer {
public static void main(String[] args) {
Counter counter = new Counter();
//开10条线程对counter加数
for (int i = 0; i < 10; i++) {
new Thread(counter).start();
}
}
}
//一个计数器
class Counter implements Runnable {
int val = 0;
public int getVal() {
return val++;
}
@Override
public void run() {
try {
//放大线程安全问题
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " is working..." + " after increment, the value is " + getVal());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
Thread-4 is working... after increment, the value is 1
Thread-8 is working... after increment, the value is 2
Thread-0 is working... after increment, the value is 0
Thread-2 is working... after increment, the value is 0
Thread-6 is working... after increment, the value is 4
Thread-3 is working... after increment, the value is 3
Thread-7 is working... after increment, the value is 5
Thread-9 is working... after increment, the value is 6
Thread-5 is working... after increment, the value is 7
Thread-1 is working... after increment, the value is 8
当然,它是有问题的。
解决办法是什么?
加锁。
加synchronize肯定能解决啦,不过我们希望在java层面解决同步问题。
因此,我们加ReentrantLock上锁:
//一个计数器
class Counter implements Runnable {
ReentrantLock lock = new ReentrantLock();
int val = 0;
public int getVal() {
return val++;
}
@Override
public void run() {
try {
Thread.sleep(200);
lock.lock();
System.out.println(Thread.currentThread().getName() + " is working..." + " after increment, the value is " + getVal());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
我们先用lock方法上锁,然后等这个方法返回后,我们去执行下面的代码,这期间其他线程一定是不能进来打断的。最后调用unlock方法释放锁。然后下一个线程才能够动起来。
自己实现
为了理解ReentrantLock。我们自己来写锁。
package reentrantlock;
/**
* 简单的锁
*
* 对外暴露lock和unlock方法来加锁和解锁
*
* author:Ocean Chou
*/
public class SimpleLock {
private AtomicInteger status = new AtomicInteger(0);
public void lock() {
while (!status.compareAndSet(0,1)) {
}
return;
}
public void unlock() {
status.set(0);
}
}
这是一把我们自己实现的锁。
我们重新走一下计数器这个task:
//一个计数器
class Counter implements Runnable {
SimpleLock lock = new SimpleLock();
int val = 0;
public int getVal() {
return val++;
}
@Override
public void run() {
try {
Thread.sleep(200);
lock.lock();
System.out.println(Thread.currentThread().getName() + " is working..." + " after increment, the value is " + getVal());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
比如10个线程进来。
首先睡200毫秒(这是为了增加抢锁的效果),然后调用lock方法。
lock
方法里调用compareAndSet
方法。
这时只有一个线程(比如线程1)能够操作成功(CAS操作),返回true
,取反为false
。
于是CAS操作成功的那个线程直接return
。
于是执行下面的代码(val++
)。
而其他线程呢?
在线程1 unlock
之前,它们一直在while
循环里面转。
这里有个问题,就是while
循环很耗资源。
Thread.sleep(200);
lock.lock();
System.out.println(Thread.currentThread().getName() + " is working..." + " after increment, the value is " + getVal());
Thread.sleep(5000);
如果让线程1在执行完逻辑之后睡5秒钟,那么其他线程是不是就一直外while
里转了?
这样的话,cpu就标高了。
一个解决办法是使用 yield();
,其他线程进入while循环后,直接让出cpu的占用。
//加锁
public void lock(){
//如果cas操作成功,则不进入while循环,直接return
while(!compareAndSet(0,1)){
yield();
}
return;
}
对于两个线程的问题,这是有效的。如果有大量线程的话,比如这次你让出cpu,下次cpu调度的还是你,这样就达不到我们出让资源的初衷。
那我们试试sleep
:
//加锁
public void lock(){
//如果cas操作成功,则不进入while循环,直接return
while(!compareAndSet(0,1)){
try {
sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return;
}
线程1只睡5秒,线程2在while循环里睡了20秒,这样就会有15秒白白被浪费了,这也不对。
现在我们用park
把SimpleLock
改得先进一点:
public class SimpleLock {
//等待的线程队列
private Queue threadQueue = new LinkedList();
//用一个status来标识上锁是否成功
//用volatile修饰是希望对status的改变是对其他线程可见的
private AtomicInteger status = new AtomicInteger(0);
//加锁
public void lock(){
//如果cas操作成功,则不进入while循环,直接return
while(!status.compareAndSet(0,1)){
mypark();
}
System.out.println(Thread.currentThread().getName() + " is going to lock...");
return;
}
private void mypark() {
if(threadQueue != null){
//把当前线程加入到等待队列
threadQueue.add(Thread.currentThread());
//释放cpu资源
releaseCpu();
}
}
private void releaseCpu() {
LockSupport.park();
}
//解锁
public void unlock(){
status.set(0);
notifyThread();
}
private void notifyThread() {
if(threadQueue != null){
Object head = threadQueue.poll();
if(head != null){
Thread headThread = (Thread) head;
System.out.println(headThread.getName() + " is unpark...and ready to execute the code...");
LockSupport.unpark(headThread);
}
}
}
}
注意到我们用了queue,这样子就可以一个一个按顺序来取了。
另外,我们还使用了Unsafe
提供的park
,它也是出让cpu资源的,但是,我们可以用unpark来人工叫醒线程。
ReentrantLock源码
好了,回到reentrantlock。
首先是这个Sync。
它是一个AQS
。
我们会重点研究。
锁有两种,公平的和非公平的。
想象这样一个一个场景:
thread1拿到了锁,然后睡了200ms,然后此间thread2,thread3,thread4都去问有没有锁可以用啊?答案当然是否(因为thread1拿着锁嘛),于是它们按照来的先后顺序进入了一个queue。
然后thread1睡好了,准备unlock了,一unlock,thread5突然进来了,它问:“有没有锁啊?”,正好有唉,于是thread5就拿到锁了,这对于等在queue中的threads是不公平的,尤其对于最先排的thread2。
如果是按来的顺序拿到锁,这就是公平的。叫做公平锁。
非公平锁:
逻辑就是直接cas。
我们主要关注公平锁。方法栈一路调过去:
比如thread1进来,getState()
为0,进入if。
这是AQS中的方法。
这个Node是AQS中的静态内部类。我们细看一下:
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
volatile Node prev;
/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
我们只需记住:
volatile Node prev;
volatile Node next;
volatile Thread thread;
这像不像一个双向链表?
我们还是来看hasQueuedPredecessors
方法。它是问:当前线程需不需要进队?
因为head
和tail
都是null,所以h != t
返回false,所以整体返回false。
所以!hasQueuedPredecessors()
为true,如果cas操作成功,则
把该线程标记成当前持有锁的线程。
然后返回true
。
于是!tryAcquire(arg)
为false。
那么
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
将不执行selfInterrupt();
方法。
然后一路return
上去就完事了。
如果线程是交替执行的,也就是说,thread1完事之后,thread2再进来,那么会经历和thread1一样的代码,head
和tail
照样是null
。
也就是说,交替执行,我们都碰不到底层代码。
这就是一把轻量级锁。它不像synchronize,不论是什么情况,都会调os函数(当然,后来它优化了)。
如果是race condition的情况,即线程竞争,那会出现什么情况呢?
比如thread1拿到锁了,然后睡了5s。
这期间,thread2来了。
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
这时候getState()
为1,进else if。
判断thread2是否为当前持有锁的线程,当然为false
。如果是thread1进来的话,else if就可以进去,然后state就会+1,这就是重入锁的逻辑了。
好,thread2返回false。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(arg)
取反为true
。
于是进入 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
。
这就是所谓的入队操作。
先看addWaiter
。
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
它用来标记有个结点正以专有模式在等待。
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
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;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
首先new一个node。
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
然后把tail
赋给pred
。于是pred
为null
。
走enq(node);
我们之前知道,一个node的主要内容就是prev,next,和thread。
现在new了一个node维护thread2。
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
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;
}
}
}
}
这就是入队。
看Daug Lea怎么说的。
* <p>To enqueue into a CLH lock, you atomically splice it in as new
* tail. To dequeue, you just set the head field.
* <pre>
* +------+ prev +-----+ +-----+
* head | | <---- | | <---- | | tail
* +------+ +-----+ +-----+
* </pre>
*
* <p>Insertion into a CLH queue requires only a single atomic
* operation on "tail", so there is a simple atomic point of
* demarcation from unqueued to queued. Similarly, dequeuing
* involves only updating the "head". However, it takes a bit
* more work for nodes to determine who their successors are,
* in part to deal with possible cancellation due to timeouts
* and interrupts.
入队的话,只要在尾巴上粘一个就行了。出队就要更新头了。
我们把封装了thread2的node传进来,进入一个死循环。
tail
赋给t,t为null
,进if。
new一个空node:
Node() { // Used to establish initial head or SHARED marker
}
然后compareAndSetHead
,使head
指向空node。
然后使tail
也指向空node。
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
private transient volatile Node tail;
第二次循环。
tail
不为null
,所以t
不为null
。
进else。
第一步,让node(thread2)的prev指向空node。
然后compareAndSetTail
。看tail
是不是t
,当然是啊。如果是的话,就把tail
设为node(thread2)(传进来的node)。
然后让t的下一个节点(next)指向node。
最后把t
返回出去,注意,我们返回的是当前节点的上一个节点(为什么要这么做呢?)。
enq(node)
方法结束。队呢排进去了。
然后上一层返回node:
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
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;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
然后进acquireQueued
方法。
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
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);
}
}
又是一个死循环。
首先拿出当前node(thread2)的上一个node,那就是head
呗。
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
也就是说,封装thread2的node问:“老子是不是第一个排队的?”
唉,是,那我就再去拿一次锁(搞不好thread1已经睡好了并且解锁了呢!)所以tryAcquire(arg)
一次。可以成功,成功了就拿到锁了,如果返回false,thread1还没完事,那么
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
看来是不得不等了。
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
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;
}
上一个节点的waitStatus
是0。
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
于是进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);
把上一个节点的waitStatus
换成SIGNAL
,也就是-1
。
然后return 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;
}
它又去问了一遍现在能不能拿到锁?(thread2看来还是挺不甘心的)。
(这就是所谓的自旋)。
要是还是拿不到,我们又要进shouldParkAfterFailedAcquire
。
这时waitStatus等于Node.SIGNAL
,于是返回true
。
那就park
,停在这儿吧。
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
好,此时thread3也来了。
假设thread1还是没有释放锁,那么thread3将经历和thread2一样的逻辑,直到:
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
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;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
这时候pred不为null
了,所以最后维护好的链表为:
排好队之后,进acquireQueued
方法。
因为final Node p = node.predecessor();
并非head
,所以thread3都没有资格去tryAcquire
。所以说,自旋只发生在thread2身上,或者说,只发生在队列中的第一个人身上。
接着进入shouldParkAfterFailedAcquire(p, node)
。
int ws = pred.waitStatus;
先要拿到thread2的waitStatus
,当然是0
。
接着 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
,
把thread2的waitStatus
设置成-1
。
然后thread3就去park
了。
(上图的第3个node的waitStatus不是-0,就是0啊)。
至此为此,有几个问题。
第一:为什么队列要维护一个thread为null的node呢?
第二:为什么thread2要自旋?
第三:为什么要由后面的线程(node)来修改前面的线程(node)的waitStatus?
- 第一个问题:
我们知道,thread1并没有进入队列。
那如果thread1释放锁,thread2拿到锁队列会变成什么样子呢?
thread1先tryRelease
:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
return true
;
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
拿到head,进入if。
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
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);
}
传进来的node是head,head的waitStatus
是-1, compareAndSetWaitStatus(node, ws, 0);
将head的waitStatus
设置为0。
然后取出thread2那个node,unpark
它。
那么之前thread2是在哪里park
的呢?
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
thread2在这里park
。
于是thread2继续往下执行。
返回Thread.interrupted();
,thread2肯定没有被打断,所以返回false。
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);
}
}
这样子就又进入了死循环。
这时候thread2就能拿到锁了。
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
然后把thread2的那个node设置为head。
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
这样就保证了这么一条原则:持有锁的线程不参与排队;为什么呢?因为设计思路是:持有锁的线程是处理业务的线程,它不属于wait queue。
-
第二个问题:就是为了尽量不要去park,因为park要调用底层函数。如果thread1正好释放了锁,那thread2就可以正好获取锁。至于为什么自旋两次,这就是性能问题。
-
第三个问题:因为睡着后的线程是动不了的,所以只能由后面的线程来帮助修改
waitStatus
。如果是自己在睡着之前改waitStatus
,可能会遇到exception的情况。
至此,我们讲了reentrantLock的上锁过程。讲了线程交替执行时reentrantLock的简便(没有park和unpark)。讲了线程竞争执行时如果thread1拿到锁,thread2和thread3排队的过程。其中thread2还要自旋两次。thread3就没有自旋了。
讲了等待队列为什么需要虚拟出一个thread==null的node作为第一个node。当thread1释放锁后,thread2持有了锁,本质上它不能算作排队的人,thread2也变成了null。现在thread3是第一个排队的人了。
好。
再来看看这种情况:
thread2通过下面代码释放锁:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
这时候锁已经是自由状态了。
但是,它还没有去unpark
下一个线程。
此时thread3排好了队,并且进入死循环询问锁的情况:
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);
}
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
因为state变成0了,所以进入:
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 order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
h!=t
返回true
,因为此时队列已经被初始化了,h指向thread2(node),tail指向thread3(node)。(s = h.next) == null
返回false
。
当前线程是来询问锁的线程,是thread3,所以s.thread != Thread.currentThread()
返回true
。
那么整体返回true
,于是cas。加锁成功。
想象一下如果不是thread3来询问的,而是thread4来询问的,那么s.thread != Thread.currentThread()
就会返回false。如此保证了公平。
暂时结束于此。