RTL 是用于管理线程并发的解决方案。接下来我们从最基本的用法来分析,为什么这个类会做到线程的原子性。一般用法 ReentrantLock rtl=new ReentranLock()。实际上内部默认的是利用非公平锁来实现。
public void lock() {
sync.lock();
}
此方法就是NonfairSync里面的lock()。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
注意这个compareAndSetState(0, 1),其实就是直接获取锁。大家注意获取锁其实就是将同步状态的state值置为1,这个同步状态的值就是stateOffset。为啥这个变量能够起到这个作用呢,原因在于这是一个private static final long stateOffset;什么意思,java里面你更改不了,但是通过cas算法确实可以直接变更,这就是同步的基石。这个跟synchronized 这个JVM里面的关键字的同步机制不一样,这个synchronized 锁的获取和释放是由JVM 直接控制。
继续看代码。这里我不分析cas 的原理,compareAndSetState(0, 1),实际调用的unsafe.compareAndSwapInt(this, stateOffset, expect, update)。这个方法很有意思,首先判断state的值是否为0,意思代表这个锁并没有被任何线程占用,那么我们可以占用,这个时候设置为1,然后接下来
setExclusiveOwnerThread(Thread.currentThread());设置当前线程占用了这个锁。如果有另外一个线程也要跑这个代码的时候,这个时候stateOffset 的值就是1了,这个时候就走到了 acquire(1);这块属于核心逻辑了。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先是tryAcquire(arg),是钩子方法,可以看到AQS 是采用模板设计模式。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果没有线程占据锁,那么直接获取,返回true.
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//这个时候获取到占据锁的线程,判断独占线程是不是自身。如果是的话。 state 加1.这个代表了非公平锁属于可重入。如果还是没有获取那么返回了false。
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;
}
acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 这个方法首先来看。addWaiter(Node.EXCLUSIVE)。
实际上是将自身线程加入到等候队列。
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的引用赋值给Node。
node.prev = pred;
//设置末尾Node为现有Node,意思就是加入到末尾。
if (compareAndSetTail(pred, node)) {
//如果加入成功,那么将现有node引用赋值给之前末尾指针的next。这样现有node就排在了原先末尾的后面。
pred.next = node;
return node;
}
}
//如果有tail和没有tail都会走这个路基。
enq(node);
return node;
}
分析enq(node)。实际上主要的也就是将Node设置为head或者设置为tail。如果没有tail那么必然没有head,这个时候直接设置head。如果有tail,那么将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;
}
}
}
}
至此addWaiter分析完毕。实际看到传入的参数是Node.EXCLUSIVE,代表将独占模式的的Node入队尾了。接下来分析acquireQueued(Node)方法。都写着final呢,这个是模板方法了。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取到node前面的那个Node,实际就是没有加Node.EXCLUSIVE之前的末尾结点。
final Node p = node.predecessor();
//如果p就是head,那么代表只有一个节点。
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//判断是否需要阻塞现场。如果阻塞线程了,那么是需要等待唤醒的。否则会一直走死循环,直到获取到锁。阻塞的前提是前面的线程信号是SIGNAL.
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//如果当前线程的前置节点为SIGNAL,代表可以阻塞了,线程阻塞会
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.
*/
//设置前置线程SIGNAL 标志。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
以上就是获取锁的流程。现在分析下解锁的流程。
public final boolean release(int arg) {
if (tryRelease(arg)) {
//如果释放成功,那么
Node h = head;
if (h != null && h.waitStatus != 0)
//下一个结点唤醒。
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease是钩子方法。只有当state==0 才能进行真正释放。
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;
}
这就是非公平锁的锁机制,为啥说非公平呢,以为不管任何线程想要获取锁,都是先去请求锁,取不到才进入等待队列。公平锁的机制和这差不多。至于说Condition机制后续再找时间分析吧。
本文深入解析ReentrantLock的工作原理,包括非公平锁的获取与释放流程、条件变量机制等,并对比synchronized关键字的不同之处。
1158

被折叠的 条评论
为什么被折叠?



