public class LockTest {
static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
new Thread(new A()).start();
lock.lock();
System.out.println("111");
Thread.sleep(20000000);
lock.unlock();
}
static class A implements Runnable{
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
System.out.println("2222");
lock.unlock();
}
}
}
默认是非公平锁
public void lock() {
sync.lock();
}
这里调用了内部类的实现
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
先看state
- 值为1表示锁已经被获取
- 值为0表示可以获取锁,在释放锁的时候也要将该值设为0
这里尝试将state设置为1,也就是获取到锁并记录当前线程。
如果获取不到,则进入acquire(1)方法,方法在父类AQS中
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 这里首先尝试获取锁 tryAcquire是子类的实现,这里也就是调用NonfairSync的tryAcquire,代码在上面已经贴出来了。
- addWaiter创建node并入队,acquireQueued方法中让头结点尝试获取锁
那么就先看看1,位于Sync中
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
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;
}
- 很明显,根据demo我们提前在主线程获取到锁,所以这里的c为1。
- 如果c为0,则通过cas将state设置为1,成功后设置当前线程并返回true。
- 如果当前线程就是获取锁的线程,那么nextc就是2,state设置为2,这里也就是可重入的方式,通过对state改变加1,来表示已经重入的次数。
- 如果c为1并且不是当前线程持有,则返回false。
接着就是AQS的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;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
- 先创建Node,参数是当前线程和mode,mode有两种排它和共享。
- 因为AQS内部维护的是双向链表,所以这里先获取前驱节点pred,那么肯定是tail,要接在最后。
- 那么我们创建的节点node的prev就是pred了,并通过cas将节点设置为tail。
- 如果前面判断失败,那么就是tail为null,也就是目前操作时内部没有节点,通过enq入队。
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;
}
}
}
}
- 如果tail是null,那就需要创建一个初始化节点,就是一个dummy node。
- 创建完dummy node,继续循环会进入else后面的代码。也就是将我们创建的节点链接上,并将其设置为tail节点。
那么结束创建node,继续进入acquireQueued
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);
}
}
- 首先获取当前节点的前驱节点,如果前驱节点是head则尝试获取锁,如果成功了则设置head为当前节点。并返回false。
- 如果不是头节点或者尝试获取锁失败了,则需要挂起。
看下shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node n
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的几个状态
CANCELLED为1,表示线程需要取消
SIGNAL为-1,表示成功节点的线程需要唤醒
CONDITION为-2,线程需要等待
PROPAGATE为-3,无条件传播
获取前驱节点的waitStatus,变量为ws。
判断如果ws为Node.SIGNAL也就是-1,则返回true,需要阻塞当前线程。
如果ws大于0,表示前驱节点已经处于cancelled,需要出队
不属于上面两种情况的其他情况,需要将前驱节点设置为SIGNAL状态
demo中ws为0,则将前驱节点设置为SIGNAL,并返回fasle。返回false后,再次循环进入该方法,发现前驱为signal,则发回true,并挂起当前线程。