ReentrantLock:
1.可重入
2.公平锁或者非公平锁
3.手动加锁,手动释放
AQS:ReentrantLock内部维护了一个AQS队列,AQS的本质是一个双向链表。AQS中存放着排队等待锁的线程对象(Node)
ReentrantLock公平锁的加锁过程
public final void acquire(int arg) {
//tryAcquire(arg):尝试当前线程加锁
//acquireQueued(addWaiter(Node.EXCLUSIVE), arg)):将当前线程加入到AQS队列
//selfInterrupt():重置线程interrupted状态
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();//线程被唤醒后,还原interrupt.因为Thread.interrupted()可能会重置interrupt
}
tryAcquire(arg) :尝试获取锁,如果能加锁成功,返回true,加锁失败返回false
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取当前锁的状态(state)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {//线程节点第一次进入的时候,waitStatus=0 或者等于 -3
//前一个线程的节点状态由当前线程设置
//原因:1.当前线程不能确定自己的线程状态,只要其他线程才能确定当前线程的状态
// 2.当前线程设置自己状态的过程可能会异常
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);//将当前线程节点的前一个的waitStatus设置为-1
}
return false;
}
parkAndCheckInterrupt():将当前线程park,加锁过程完成
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);//park当前线程
//当前线程被unpark之后,继续从这里执行
//Thread.interrupted():
//如果当前线程被其他线程调用了interrupt方法,Thread.interrupted()返回true,同时将interrupt重置
return Thread.interrupted();
}
ReentrantLock的解锁过程
public void unlock() {
sync.release(1);
}
tryRelease(arg)://尝试释放锁
protected final boolean tryRelease(int releases) {//releases = 1
//如果state = 1,说明当前线程没有重入, 大于1说明线程重入
int c = getState() - releases;//释放lock之后,当前锁的状态 ,如果= 0 则当前线程不再持有锁,大于1说明当前线程依然持有锁
if (Thread.currentThread() != getExclusiveOwnerThread())//正常来说说不可能的
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//当前线程不再持有锁,将exclusiveOwnerThread设置为null,释放锁成功
free = true;
setExclusiveOwnerThread(null);
}
setState(c);//设置锁状态
return free;
}
release(int arg):释放锁
public final boolean release(int arg) {
//尝试释放锁,释放锁成功,tryRelease会返回true。这里有两种情况
//1.当前线程没有重入
//2.线程重入了,但是全部释放了。所以锁重入了多少次就要释放多少次。否则该线程就会一致持有锁
if (tryRelease(arg)) {
//唤醒下一个线程
Node h = head;
//对h != null && h.waitStatus != 0的情况分析
// 1. h == null,表明了AQS队列没有初始化过,对于AQS没有初始化的情况有两种
// a.只要一个线程获取锁
// b.多个线程在获取锁,但是它们直接是交替执行的,不需要进入AQS中排队等待。也就是说这些线程加锁时第一次tryAcquire时都会加锁成功
// 2. h != null && h.waitStatus != 0,表示了AQS已经被初始化过了,并且队列中还有等待唤醒的线程
//原因:h != null说明head是存在的
// h.waitStatus != 0 (也就是 = -1),因为节点的waitStatus是由下一个节点设置的,
//所以waitStatus不等于默认值0就说明了下一个节点是存在的
if (h != null && h.waitStatus != 0)
//唤醒下一个可被唤醒的节点
unparkSuccessor(h);
return true;
}
return false;
}
unparkSuccessor(h):唤醒头节点的下一个可被唤醒的节点(不一定是头节点的下一个)
private void unparkSuccessor(Node node) {//node = head
int ws = node.waitStatus;
if (ws < 0)//重置head节点的waitStatus = 0
node.compareAndSetWaitStatus(ws, 0);
Node s = node.next;//头节点的下一个节点
//这一块代码几乎可以不需要考虑,因为线程被cancel只会出现在我们自己实现AQS出异常的时候调用cancelAcquire,
//jdk实现的AQS不会出现
if (s == null || s.waitStatus > 0) {//下一个节点不存在或者是被canceled状态,就从队尾开始找最靠近头节点的可被唤醒的节点
s = null;
for (Node p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
//唤醒线程
LockSupport.unpark(s.thread);
}
unparkSuccessor(h)执行完成之后,park的地方继续执行,继续执行tryAcquire(),尝试加锁过程。加锁成功后将当前唤醒的节点中thread设置为null。lock解锁完成
head中的thread=null有两层含义:1.空节点 2.当前持有锁的线程
总结:当第一个线程来请求加锁的时候,可以直接拿到锁,而没有去初始化AQS队列。当第二个线程请求加锁时候,判断当前锁有没有被某一个线程持有,如果没有其他线程持有,就去查看AQS队列中有没有线程在等待唤醒,如果AQS没有被初始化或者没有线程在等待唤醒,那么第二个线程就可以获取锁。如果其他线程已经持有了锁,再看第二个线程是不是当前持有锁的线程,如果是的话,当前线程也可以加锁成功,同时lock的state+1,表示锁的重入次数+1。如果当前持有锁的线程和第二个线程不是同一个线程,那么第二个线程要加入的AQS队列中去。在加入到队列的过程中,如果第二个线程排在第二位(head.next指向的节点)会去再次尝试获取锁,如果不是的话就直接加入到AQS队列中。加入AQS时会将当前节点的前一个节点的waitStatus设置为-1,同时将当前节点设置为tail,等待被唤醒。AQS队列中的节点在得到锁之后,会将原来的head节点引用断开,将当前节点设置为新的head,同时将节点的thread设置为null.
当持有锁的对象释放锁时,会讲head节点的waitStatus设置为0,同时查看AQS是否存在,或者AQS中是否有等待的线程,如果有等待的线程那么就唤醒第一个可被唤醒的线程。
非公平锁与公平锁加锁过程的区别
公平锁:公平锁加锁时会检查AQS中是否有排队等待唤醒的线程,如果有的话,当前线程会加入到AQS中排队,而不是尝试获取锁
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;
}
}
非公平锁:加锁时不会检查AQS中是否有排队等待唤醒的队列,而是直接尝试获取锁,如果获取不到锁才会加入到AQS中排队
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) {
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;
}
公平锁与非公平锁的区别就是在没有线程持有锁的情况下,公平锁回去查看AQS中是否有线程在排队,如果有线程在排队,那么当前线程也加入排队。而非公平锁则是先进行一次尝试加锁,如果加锁失败了才会加入到AQS中排队