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

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

JAVA并发编程系列(七)深入理解AQS并发案例_第1张图片

此时AQS内部数据为: 

JAVA并发编程系列(七)深入理解AQS并发案例_第2张图片

线程二线程三加锁失败:

JAVA并发编程系列(七)深入理解AQS并发案例_第3张图片

有图可以看出,等待队列中的节点Node是一个双向链表,这里SIGNALNodewaitStatus属性,Node中还有一个nextWaiter属性。

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

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

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,且设置对象独占锁线程为当前线程。如下所示:

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变量必然会失败。此时AQSFIFO(First In First Out 先进先出)队列中数据如图所示:

JAVA并发编程系列(七)深入理解AQS并发案例_第4张图片

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

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

非公平锁执行流程:

JAVA并发编程系列(七)深入理解AQS并发案例_第5张图片

这里我们还是用之前的线程模型来举例子,当线程二释放锁的时候,唤醒被挂起的线程三线程三执行tryAcquire()方法使用CAS操作来尝试修改state值,如果此时又来了一个线程四也来执行加锁操作,同样会执行tryAcquire()方法。

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

 

公平锁执行流程:

JAVA并发编程系列(七)深入理解AQS并发案例_第6张图片

公平锁在加锁的时候,会先判断AQS等待队列中是存在节点,如果存在节点则会直接入队等待,具体代码如下.

公平锁在获取锁是也是首先会执行acquire()方法,只不过公平锁单独实现了tryAcquire()方法;、

总结:

非公平锁公平锁的区别:非公平锁性能高于公平锁性能。非公平锁可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量

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

你可能感兴趣的:(JAVA并发编程系列(七)深入理解AQS并发案例)