1.竞争锁 我们先从公平锁入手
public void lock() {
// sync的实例是new FairSync()
sync.acquire(1);
}
// 加锁的代码就是这几行
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
上述代码可以拆分成以下几段:
// 竞争锁
tryAcquire(arg)
// 加入等待队列
addWaiter(Node.EXCLUSIVE)
// 阻塞等待
acquireQueued(node, arg)
复制代码
- 竞争锁
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前state状态
int c = getState();
// 如果当前state是没有任何线程抢占的话
if (c == 0) {
// 如果等待队列中有任何一个等待的节点,都不会抢占锁
if (!hasQueuedPredecessors() &&
// CAS抢占锁成功
compareAndSetState(0, acquires)) {
// 抢占成功后,标记当前线程已经抢占到锁了。
setExclusiveOwnerThread(current);
// 返回加锁成功
return true;
}
}
// 如果是同一个线程重复加锁的情况下
else if (current == getExclusiveOwnerThread()) {
// 在这种情况下,只是简单地操作state
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 因为当前线程已经加锁成功了,再次加锁的话,直接在state上增加加锁次数即可。
setState(nextc);
// 返回加锁成功
return true;
}
// 如果已经有别的线程加锁了,或者还有很多线程在排队等待,那么返回false加锁失败。
return false;
}
复制代码
上述代码分几部分:
- 如果当前state=0,也就是没有任何线程抢占锁的情况下
1.1: 没有等待队列的情况下,可以CAS抢占锁
1.2: 有等待队列的话,该队列中第一个等待节点不是当前线程,不可以抢占锁,因为这是公平锁。
如果当前等待队列中还有任意节点,并且当前节点中的线程不是当前线程,说明有其他线程处于等待过程中,那么当前线程就应该乖乖排队去。
1.3: 有等待队列,并且当前第一个等待节点就是当前线程,可以抢占锁。这种情况会出现在线程刚从阻塞中被唤醒的时候。
- 假如当前线程是被刚刚唤醒的,并且它处于等待队列中的第一个等待的位置,那么这个时候是可以去抢占锁的。
- 如果已经抢占了锁的线程就是当前线程。这种情况我们叫做重入。
示例如下:
ReentrantLock lock = new ReentrantLock();
try {
// 加锁
lock.lock();
// 执行业务逻辑
System.out.println("获取的锁");
try {
// 再次获取锁
lock.lock();
// 执行业务逻辑
System.out.println("再次获取的锁");
} finally {
// 解锁
lock.unlock();
}
} finally {
// 解锁
lock.unlock();
}
复制代码
小结一下:
- 如果当前锁未被抢占