重入锁
同一个线程可以多次获取同一个锁。synchronize关键字隐士的支持重入。JUC包下重入锁为ReentantLock,显示的提供来人锁重入功能,并且提供了获取锁的公平与非公平方式。
在时间上,先请求获取锁的线程一定能够先与其后的线程获取锁为公平锁,否则为非公平锁。公平锁减少了发生饥饿的概率,但是效率低。非公平锁则提升了TPS,缺会使饥饿问题概率提升。
重入的实现在于获取锁时,锁会保存当前线程,当下个线程请求获取锁时,对线程进行判断,如果为当前线程,则能够获取成功,而同步状态增加。在释放锁时也要多次进行释放,对同步状态进行判断,直到为state==0才会释放锁。
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;
}
公平性与非公平性的获取锁的区别在于获取锁前会不会判断该线程是不是等待队列的头结点。
// FairSync获取锁 公平锁
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;
}
// NonfairSync 获取锁 非公平锁
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;
}
释放锁的机制都一样 多次释放 直到state == 0
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 最终同步状态为0才释放锁
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
公平锁保证了锁的获取按照FIFO原则,避免了饥饿问题,但是会进行大量的线程切换。非公平锁索然可能造成饥饿问题,但是大大减少了线程切换,保证了更大的吞吐量。
在索金乡算则时,如果需要创建大量不同的线程,而且需要线程都能够获取到锁进行执行,为了防止饥饿问题可以选择公平性锁。如果存在很多相同线程多次获取锁的场景,则选择非公平锁较好。