什么是可重入锁呢
顾名思义,就是可以重复进入的锁,学过操作系统或者计组的可参照理解pv,或者多重中断。
demo1(){
lock(); //第一次锁
demo2(){
lock(); // 第二次锁
unlock();
}
unlock();
}
文章目录
- ReentrantLock
- lock 加锁
- 1. ReentrantLock.lock()
- 2. sync.lock()
- 3. unfairSync.lock()
- 4. AQS.acquire(1)
- unlock 解锁
- 1. ReentrantLock.unlock()
- 2. AQS.release(1)
- 3. syn.tryRelease(1)
- 总结
ReentrantLock
lock 加锁
1. ReentrantLock.lock()
直接从lock()入手翻阅源码
public void lock() {
sync.lock();
}
它调用的是sync.lock();
2. sync.lock()
在ReentrantLock初始化时,默认是非公平锁,有参构造true则是公平锁
非公平锁:来个线程就先试试能不能插队,不能插队才去后面排队
公平锁:线程都乖乖去后面排队去,不准插队
总而言之,sync就是个内部非公平/公平锁。
再往下看,由于lock()是抽象方法,而sync默认是非公平锁。
调用unfairSync.lock()
3. unfairSync.lock()
final void lock() {
// 若CAS抢到锁,记录设置当前线程
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 若没抢到锁
acquire(1);
}
点击compareAndSetState
这就是CAS获取锁,unsafe是JDK中用于硬件实现CAS的操作。
不用管它,只需要理解这里就是CAS操作。
若state==0,则更新为1,并且设置好排他线程。即该线程成功抢到锁
那如果没抢到锁呢
4. AQS.acquire(1)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
先执行tryAcquire(),可以理解为再次抢锁。
- 如果成功,返回为true,再加个!,成了false,后面就不用执行
- 如果失败,则执行后面acquireQueued(),即进入等待队列
这里nonfairSync重写方法,直接调用nonfairSync.tryAcquire(1)
继续往下调用nonfairTryAcquire(1)
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果state=0,再重新尝试一下看能不能抢到锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 如果state不为0
// 如果这个线程就是之前已经抢到锁的那个,它又要加锁,重入!
else if (current == getExclusiveOwnerThread()) {
// state递加上去
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
简单理解就是
- 如果state==0,再重新CAS抢救一下,看能不能抢到锁,抢到了那就成功
- 如果state不为0,说明已经被抢了。但是如果那个抢到锁的线程是自己,自己又重入了,那state+1,再次加锁,成功。
- 如果那个抢到锁的不是自己,加锁失败。
如果加锁失败,则方法返回到AQS.acquire(1),还需要执行if后面的判断。
简单看下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
// tail即尾结点
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
这里维护着一个双向链表,简单来说就是把结点放到链表的尾部,并且更新尾结点。
加锁失败了,把这个线程放在表尾,乖乖排队吧。
这就是加锁的所有过程。
unlock 解锁
1. ReentrantLock.unlock()
很明显,看来这里是将之前的state一个个减1减回来
2. AQS.release(1)
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也就是解锁了
3. syn.tryRelease(1)
就是将state-1,进行解锁操作。由于可能被可重入了,state-1后不一定为0;如果为0则将记录的线程清空。
解锁很好理解,就不详细赘述了。
总结
lock:
- CAS获取锁,若没有线程占用锁(state==0),加锁成功并记录当前线程是有锁线程(两次,开始一次,acquire()中又一次)
- 若state值不为0,说明锁已经被占用,则判断当前线程是否是有锁线程,若是则重入(state+1)
- 否则加锁失败,入队等待
unlock:
- 判断当前线程是否是有锁线程,不是则抛出异常
- state-1, 若-1后state值为0则解锁成功,返回true
- 若-1后state不为0,则返回false