您的位置:首页 > 编程语言 > Java开发

JAVA并发编程系列(七)深入理解AQS并发案例

2020-06-05 08:07 113 查看

并发例子:比如三个线程并发通过AQS进行抢锁具体流程如下。

此时

AQS
内部数据为: 

线程二线程三加锁失败:

有图可以看出,等待队列中的节点

Node
是一个双向链表,这里
SIGNAL
Node
waitStatus
属性,
Node
中还有一个
nextWaiter
属性。

具体看下抢占锁代码实现:

java.util.concurrent.locks.ReentrantLock .NonfairSync:

[code]static final class NonfairSync extends Sync {

final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}

这里使用的ReentrantLock非公平锁,线程进来直接利用

CAS
尝试抢占锁,如果抢占成功
state
值回被改为1,且设置对象独占锁线程为当前线程。如下所示:

[code]protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}

线程二抢占锁失败

我们按照真实场景来分析,线程一抢占锁成功后,

state
变为1,线程二通过
CAS
修改
state
变量必然会失败。此时
AQS
FIFO
(First In First Out 先进先出)队列中数据如图所示:

 简单说一下非公平锁的实现原理:

AQS内的ReentrantLock默认是基于非公平锁实现的,先用一张图来解释公平锁非公平锁的区别:

非公平锁执行流程:

这里我们还是用之前的线程模型来举例子,当线程二释放锁的时候,唤醒被挂起的线程三线程三执行

tryAcquire()
方法使用
CAS
操作来尝试修改
state
值,如果此时又来了一个线程四也来执行加锁操作,同样会执行
tryAcquire()
方法。

这种情况就会出现竞争,线程四如果获取锁成功,线程三仍然需要待在等待队列中被挂起。这就是所谓的非公平锁线程三辛辛苦苦排队等到自己获取锁,却眼巴巴的看到线程四插队获取到了锁。

 

公平锁执行流程:

公平锁在加锁的时候,会先判断

AQS
等待队列中是存在节点,如果存在节点则会直接入队等待,具体代码如下.

公平锁在获取锁是也是首先会执行

acquire()
方法,只不过公平锁单独实现了
tryAcquire()
方法;、

总结:

非公平锁公平锁的区别:非公平锁性能高于公平锁性能。非公平锁可以减少

CPU
唤醒线程的开销,整体的吞吐效率会高点,
CPU
也不必取唤醒所有线程,会减少唤起线程的数量

非公平锁性能虽然优于公平锁,但是会存在导致线程饥饿的情况。在最坏的情况下,可能存在某个线程一直获取不到锁。不过相比性能而言,饥饿问题可以暂时忽略,这可能就是

ReentrantLock
默认创建非公平锁的原因之一了。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: