ReentrantLock

Catalogue
  1. 1. 可重入锁
  2. 2. 公平锁和非公平锁
    1. 2.1. 公平锁和非公平锁的区别

ReentrantLock实现了Lock的接口。ReentrantLock相比synchronized提供了更多的功能。Lock接口的方法如下

  • lock
  • lockInterruptibly,可中断的锁获取操作,在获取锁的过程中,线程可以响应中断。被中断时会抛出InterruptedException。
  • tryLock,非阻塞的获取锁,获取不到就立即返回。
  • tryLock(time),超时获取锁,以下情况会返回:时间内获取到了锁,时间内被中断,时间到了没有获取到锁。
  • unlock
  • condition 获取条件实例。

可重入锁

ReentrantLock是可重入锁,什么是可重入锁呢,可重入锁是可重复可递归调用的锁,同一个线程外层函数获取到锁后,内层函数仍然有获取锁的代码,但不受影响。如果锁不可重入,则在线程再次调用获取锁的方法时,会发现锁被持有而发生死锁。如下代码,同一个线程调用lock方法两次,第二次则会发生死锁。

1
2
3
4
5
6
7
8
9
public void lock() {
Thread current = Thread.currentThread();
//这句是很经典的“自旋”语法
for (;;) {
if (!owner.compareAndSet(null, current)) {
return;
}
}
}

ReentrantLock通过一个计数器来实现可重入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}

为什么要用计数器,因为获取多少次锁就要释放多少次,如果最里面一层释放了锁就直接把锁释放掉了,那么原来外层有锁的逻辑就被改变了,外层此时处于没锁的状态。

公平锁和非公平锁

ReentrantLock提供了两种锁的实现。公平锁和非公平锁,所谓公平锁是指线程获取锁的顺序按照排队顺序来,而非公平锁获取锁的顺序不定。

Sync继承自AQS,而FairSyncNonfairSync继承自Sync来实现公平锁和非公平锁。

先来看看非公平锁的lock方法,公平锁和非公平锁也就这两个方法有区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
final void lock() {
// 快速CAS来获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// CAS获取失败通过AQS中的acquire方法来获取,将state设为1
acquire(1);
}

protected final boolean tryAcquire(int acquires) {
// 参看上节中代码,支持了可重入
return nonfairTryAcquire(acquires);
}

公平锁的lock方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
final void lock() {
acquire(1);
}

/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 没有获取到锁
if (c == 0) {
// 判断前面没有节点才会去获取锁,前面有节点时acquire方法会将其加入到同步队列,进队列后就可以保证FIFO。两者结合保证了公平锁的实现。
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;
}

两种锁的unlock方法一样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void unlock() {
sync.release(1);
}

protected final boolean tryRelease(int releases) {
// 计数器减1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 表示完全释放掉锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 设置state为减后的
setState(c);
return free;
}

公平锁和非公平锁的区别

公平锁的整体吞吐量往往不高,但是可以防止饥饿现象。非公平锁线程切换次数要少些,所以吞吐量较高。