一、ReentrantLock
ReentrantLock可重入锁,这里我们主要分析它的加锁与解锁过程。在分析之前,先介绍几个对象。
Sync继承了AbstractQueuedSynchronizer(以下称AQS队列),在AQS队列中,有以下属性:
- head:队头
- tail:队尾
- state:锁的状态,默认0
- exclusiveOwnerThread:持有锁的线程(在AbstractQueuedSynchronizer的父类AbstractOwnableSynchronizer中)
- Node:节点,队列中存放的元素
- thread:node所标识的线程
- prev:node的前一个节点
- next:node的下一个节点
- waitStatus:节点的一个状态,用来判断节点是否在park
二、加锁过程
先分析公平锁的加锁过程,默认是非公平的。
1、当只有一个线程t1的情况下:
public class Demo1 {
static ReentrantLock lock = new ReentrantLock(true);
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try{
lock.lock();
say();
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
},"t1");
t1.start();
}
private static void say(){
System.out.println("========"+Thread.currentThread().getName());
}
}
源码点进去,调用链为:
java.util.concurrent.locks.ReentrantLock.Sync#lock
java.util.concurrent.locks.ReentrantLock.FairSync#lock
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
1、acquire(1);
public final void acquire(int arg) {
//如果tryAcquire返回true表示加锁成功,就不会走后面的代码了
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
int c = getState(); //拿到当前锁的状态,默认是0
if (c == 0) {
//hasQueuedPredecessors是判断t1是否需要去排队;如果队列中有线程在排队那么我也排队,否则我就通过cas去上锁。
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//上锁成功,将锁的线程持有者设为当前线程
setExclusiveOwnerThread(current);
//加锁成功返回true
return true;
}
}
//如果c!=0,表示当前已经有线程持有锁,并且还没有释放;判断持有锁的线程是否等于当前线程,也就是重入锁。
else if (current == getExclusiveOwnerThread()) {
//是重入锁,就更新c=c+1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
hasQueuedPredecessors,判断队列中是否为空
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;
//此时head=tail=null 所以返回false
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
小结:在只有一个线程的情况下,上锁成功,不会初始化AQS队列。
2、假设在t1还未释放锁的前提下,t2来了。调用lock.lock();
前面的流程都一样,此时当我们调用tryAcquire这个方法时,此时c=1,并且锁的持有者是t1线程,所有会返回false。返回false会执行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)这个方法进行排队。
private Node addWaiter(Node mode) {
//将当前线程封装成一个node
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//获取队尾node
Node pred = tail;
//判断队尾是否为空
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//此时的pred肯定等于null,因为我们还没有对其进行初始化。调用enq(node)进行初始化
enq(node);
return node;
}
enq(node)初始化队列
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//判断队尾是否为空
if (t == null) { // Must initialize
//为空,调用CAS进行初始化,head=tail
if (compareAndSetHead(new Node()))
tail = head;
} else {
//这里是for循环,所有第二次会进这里,因为tail已经不等于null了
//将t2线程所标识的node前一个node指向tail
node.prev = t;
//将t2线程所标识的node节点设为tail节点
if (compareAndSetTail(t, node)) {
//将head节点的下一个指向t2 Node
t.next = node;
return t;
}
}
}
}
acquireQueued(node),进行排队处理
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//拿出当前节点的前一个
final Node p = node.predecessor();
//这里的p=head 但是tryAcquire(arg) 会返回false,因为t1还没有释放锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//shouldParkAfterFailedAcquire判断是否要进行排队
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire;判断在加锁失败后线程是否需要park;由于外层是for (;?,所以会循环调用这个方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//默认0
if (ws == Node.SIGNAL)
//第二次进来的时候,waitStatus=-1 返回true
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//第一次进行的时候进行cas,将head的waitStatus状态修改成-1
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
shouldParkAfterFailedAcquire返回true,就会调用parkAndCheckInterrupt(),进行排队;
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
3、假设在t1还未释放锁的前提下,t3来了。调用lock.lock();
tryAcquire加锁会失败;进 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))的addWaiter(Node.EXCLUSIVE), arg)方法
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;
//队尾等于t2
if (pred != null) {
//当前node的前一个指向t2
node.prev = pred;
//将t3设置成队尾
if (compareAndSetTail(pred, node)) {
//t2的下一个为t3
pred.next = node;
return node;
}
}
enq(node);
return node;
}
再看acquireQueued方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//t3的前一个是t2
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//直接调用shouldParkAfterFailedAcquire将t2所表示的node的waitStatus改为-1,然后park
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
非公平锁的加锁过程
非公平锁的加锁过程相对于公平锁来说,就是一开始的时候就尝试去加锁修改状态值,而不是直接去看是否要排队;
而一旦加锁失败,就会走和公平锁一样的逻辑;一朝排队就会永远排队。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
三、解锁过程
解锁过程不分公平锁和非公平锁
t1释放锁,lock.unlock();
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(1);
很简单,获取当前锁的state减1;并判断减1之后的结果是否等于0
等于0—锁为自由锁, setExclusiveOwnerThread(null);将当前锁的线程持有者置为null
不等于0----更改state会直接返回,意味着是重入锁,重入多少次,就解锁多少次;
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;
}
tryRelease(1);解锁成功,会继续 if (h != null && h.waitStatus != 0)判断;
判断对头是否为空----肯定不为空;
h.waitStatus != 0------此时应该等于-1;
所以满足条件,会调用unparkSuccessor(h);
private void unparkSuccessor(Node node) {
//拿出头节点的ws
int ws = node.waitStatus;
if (ws < 0)
//小于0就通过cas置为0
compareAndSetWaitStatus(node, ws, 0);
//获取下一个节点t2
Node s = node.next;
//t2不等于null t2.ws=-1
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)
//调用unpark唤醒t2
LockSupport.unpark(s.thread);
}
这里会调用 LockSupport.unpark(s.thread);唤醒线程t2,所以代码会接着下面的代码代码执行
唤醒t2线程之后,再分析acquireQueued方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//t2线程唤醒后
//获取头节点
final Node p = node.predecessor();
//头节点不为空,并且加锁成功
if (p == head && tryAcquire(arg)) {
setHead(node);
//下面的是setHead(node);中的代码
//head = node; 设置当前t2所在的node为头节点
//node.thread = null; 设置t2所在的node的thread为空;因为当前线程已经拿到锁了,所以不用再引用它了
//node.prev = null; 设置t2所在的node的prev为null
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//修改标识,为了parkAndCheckInterrupt方法复用,当调用lock.lockInterruptibly();时,这里可以响应Interrupt异常
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
四、总结
简单总结一下(最好能自己走一遍源码)
1、加锁过程
先判断是否有线程加锁;是否重入等情况
没有线程加锁→判断是否有线程在队列中排队;没有的话直接加锁
加锁失败→如果是第一线程就初始化队列,否在就将自己放入队尾→判断自己是不是在第二个→在的话就尝试加锁,不在或者加锁失败的修改前一个节点的ws为-1,然后park。
2、解锁过程
更新state;判断是否重入,是重入的话就直接更新state,返回false;
解锁成功→判断头节点是否为空并且头节点的ws不等于0→更新头节点的ws为0→获取第二个节点→如果第二个节点不为空→直接唤醒第二个节点所标识的线程;
唤醒之后→判断自己是不是在第二个节点→是的话就直接加锁→加锁成功→将自己设为头节点,thread指向null,prev指向null→然后返回