最近在阅读Java的并行框架,从Executor框架->各种锁实现(Synchronized、ReentrantLock、Sempare等)->AQS框架->LockSupport类->UnSafe类,折磨了我一大段时间,在这里用一个ReentrantLock类中的公平锁与非公平锁做个总结吧。
static final class NonfairSync extends Sync {
private
static
final
long
serialVersionUID
= 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final
void
lock() {
//如果现在没有线程持有,则让当前线程运行
if
(compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//直接请求资源,如果有则运行,否则阻塞
acquire(1);
}
protected
final
boolean
tryAcquire(
int
acquires) {
return
nonfairTryAcquire(acquires);
}
}
/**
* Sync object for fair locks
*/
static
final
class
FairSync
extends
Sync {
private
static
final
long
serialVersionUID
= -3000897897090466540L;
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) {
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
;
}
}
该方法会首先判断当前状态,如果c==0说明没有线程正在竞争该锁,如果不c !=0 说明有线程正拥有了该锁。
如果发现c==0,则通过CAS设置该状态值为acquires,acquires的初始调用值为1,每次线程重入该锁都会+1,每次unlock 都会-1,但为0时释放锁。如果CAS设置成功,则可以预计其他任何线程调用CAS都不会再成功,也就认为当前线程得到了该锁,也作为Running线 程,很显然这个Running线程并未进入等待队列。
如果c !=0 但发现自己已经拥有锁,只是简单地++acquires,并修改status值,但因为没有竞争,所以通过setStatus修改,而非CAS,也就是说这段代码实现了偏向锁的功能,并且实现的非常漂亮。
public
final
boolean
release (
int
arg) {
if
(tryRelease(arg)) {
Node h =
head
;
if
(h !=
null
&& h.
waitStatus
!= 0)
unparkSuccessor(h);
return
true
;
}
return
false
;
}
protected
final
boolean
tryRelease(
int
releases) {
int
c = getState() - releases;
if
(Thread.currentThread() != getExclusiveOwnerThread())
throw
new
IllegalMonitorStateException();
boolean
free =
false
;
if
(c == 0) {
free =
true
;
setExclusiveOwnerThread(
null
);
}
setState(c);
return
free;
}
从无限循环的代码可以看出,并不是得到解锁的线程一定能获得锁,必须在第6行中调用tryAccquire重新竞争,因为锁是非公平的,有可能被新 加入的线程获得,从而导致刚被唤醒的线程再次被阻塞,这个细节充分体现了“非公平”的精髓。通过之后将要介绍的解锁机制会看到,第一个被解锁的线程就是 Head,因此p == head的判断基本都会成功。
至此可以看到,把tryAcquire方法延迟到子类中实现的做法非常精妙并具有极强的可扩展性,令人叹为观止!当然精妙的不是这个Templae设计模式,而是Doug Lea对锁结构的精心布局。