核心结构
通过Sync静态内部类继承AQS实现锁的机制,两个子类FairSync 和 NonfairSync 分别对应公平和非公平锁。 有关AQS的原理参考juejin.cn/post/748158... ,本文涉及AQS的方法不再重复分析。
加锁
NonfairSync 非公平锁加锁
scss
final void lock() {
//CAS设置为1,成功加锁成功
if (compareAndSetState(0, 1))
//设置独占线程
setExclusiveOwnerThread(Thread.currentThread());
else
//否则尝试获取锁
//AQS中的方法
acquire(1);
}
acquire(int arg)
scss
public final void acquire(int arg) {
if (
//尝试获获取锁
!tryAcquire(arg) &&
//失败加入同步队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//需要中断则中断
selfInterrupt();
}
arduino
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
java
final boolean nonfairTryAcquire(int acquires) {
//当前线程
final Thread current = Thread.currentThread();
//获取状态
int c = getState();
//状态为0说明之前获取锁的线程已经释放,可以去加锁操作了
if (c == 0) {
//将state设置为1,成功则加锁成功
if (compareAndSetState(0, acquires)) {
//设置当前线程独占标识
setExclusiveOwnerThread(current);
//返回加锁成功
return true;
}
}
//是独占线程又来获取锁了
else if (current == getExclusiveOwnerThread()) {
//状态为之前的状态值加1
int nextc = c + acquires;
//无效状态抛异常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//设置状态值
setState(nextc);
//返回加锁成功
return true;
}
//加锁失败
return false;
}
非公平锁加锁逻辑总结:
- 先尝试获取锁,如果加锁成功设置独占标识然后返回,失败则调用AQS中的方法尝试获取锁;
- 尝试获取锁,判断状态为0说明没有线程在执行同步代码块,可以去加锁,成功设置独占标识;判断是否是获取锁的线程再次去获取锁,是的话在之前的状态值加1(锁的重入)
- 获取锁失败加入同步队列,当前加锁节点的前驱节点是头节点再次尝试获取锁,否则自旋排队直到前驱节点是头节点。
FairSync 公平锁加锁
lock()
scss
final void lock() {
acquire(1);
}
scss
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
java
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;
}
}
java
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return
//h==t的话说明队列是空的或者队列仅有一个无效等待的节点
//h!=t说明同步队列中存在有效节点在排队
h != t &&
//说明头节点已有值(end()方法入队时候pre已经设置了值,但tail还没来得及设置,这时候认为有节点在排队)
((s = h.next) == null ||
//不等于当前线程,说明有其他节点在排队
s.thread != Thread.currentThread());
}
公平锁加锁逻辑总结: 区别在于hasQueuedPredecessors()方法
解锁
release(int arg)
java
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒后继线程
unparkSuccessor(h);
return true;
}
return false;
}
scss
protected final boolean tryRelease(int releases) {
//状态减1
int c = getState() - releases;
//必须要是加锁的线程去解锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//状态是0说明释放锁成功了
if (c == 0) {
free = true;
//清楚独占线程标识
setExclusiveOwnerThread(null);
}
//设置state值
setState(c);
return free;
}
解锁逻辑总结: 公平锁和非公平锁解锁是相同的逻辑,把state值减为0,解锁成功。
Condition 操作
arduino
final ConditionObject newCondition() {
return new ConditionObject();
}
csharp
//AQS中的
public ConditionObject() { }
对于ReentrantLock的condition 等待和唤醒机制是AQS中的代码,本文不再重复分析,简单总结下逻辑。
- 等待await()。构建一个条件节点加入到条件队列,释放锁,阻塞park()线程直到唤醒signal()重新获取锁。
- 唤醒signal()。从条件队列对头节点迁移到同步队列队尾并取消阻塞unpark()