看了下AQS的源码,有点简单,不适宜简略入门,我总结了下。

概述

Java中的大部分同步类(Lock、Semaphore、ReentrantLock等)都是基于AbstractQueuedSynchronizer(简称为AQS)实现的。AQS是一种提供了原子式治理同步状态、阻塞和唤醒线程性能以及队列模型的简略框架。

从ReentrantLock独占模式看AQS原理

public void test () throw Exception {    //初始化    ReentrantLock lock = new ReentrantLock(true);    //加锁    lock.lock();    try {        ...    } finally {        lock.unlock();    }}

lock()


看看非偏心锁的实现:

外围流程从这里开始:

1.compareAndSetState():线程进来间接利用CAS尝试抢占锁;setExclusiveOwnerThread():如果抢占胜利state值会被改为1,且设置对象独占锁线程为以后线程

2.acquire(1):若利用CAS尝试抢占锁失败,也就是获取锁失败,则进入Acquire办法进行后续解决
Acquire办法实现:

1.tryAcquire():再次尝试获取锁,如果加锁胜利则返回true,不再执行以下步骤,否则继续执行以下步骤
2.addWaiter():走到这里阐明加锁失败,创立一个Node节点绑定以后的线程,退出到一个FIFO的双向链表中,而后返回这个Node
3.acquireQueued():这个办法会先判断以后传入的Node对应的前置节点是否为head节点,如果是则尝试加锁,如果加锁失败或者Node的前置节点不是head节点,用LockSupport.park()挂起以后线程。
上述流程图:

unlock()



1.tryRelease():state被设置成0,Lock对象的独占锁被设置为null
2.unparkSuccessor():唤醒head的后置节点,被唤醒的线程二会接着尝试获取锁,用CAS指令批改state数据。
上述流程图:

总结:
AQS中 保护了一个volatile int state(代表共享资源)和一个FIFO线程期待队列(多线程争用资源被阻塞时会进入此队列)。
这里volatile可能保障多线程下的可见性,当state=1则代表以后对象锁曾经被占有,其余线程来加锁时则会失败,加锁失败的线程会被放入一个FIFO的期待队列中,比列会被UNSAFE.park()操作挂起,期待其余获取锁的线程开释锁才可能被唤醒。
另外state的操作都是通过CAS来保障其并发批改的安全性。

非偏心锁和偏心锁

线程二开释锁的时候,唤醒被挂起的线程三线程三执行tryAcquire()办法应用CAS操作来尝试批改state值,如果此时又来了一个线程四也来执行加锁操作,同样会执行tryAcquire()办法。这种状况就会呈现竞争,线程四如果获取锁胜利,线程三依然须要待在期待队列中被挂起。这就是所谓的非偏心锁线程三辛辛苦苦排队等到本人获取锁,却眼巴巴的看到线程四插队获取到了锁。
非偏心锁执行流程:

偏心锁在加锁的时候,会先判断AQS期待队列中是存在节点,如果存在其余期待线程,那么本人也会退出到期待队列尾部,做到真正的先来后到,有序加锁。
偏心锁执行流程:

非偏心锁和偏心锁的区别:
非偏心锁性能高于偏心锁性能。非偏心锁能够缩小CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不用取唤醒所有线程,会缩小唤起线程的数量
非偏心锁性能尽管优于偏心锁,然而会存在导致线程饥饿的状况。在最坏的状况下,可能存在某个线程始终获取不到锁。不过相比性能而言,饥饿问题能够临时疏忽,这可能就是ReentrantLock默认创立非偏心锁的起因之一了。

从CountDownLatch共享模式看AQS原理

    void test() throws Exception {        CountDownLatch latch = new CountDownLatch(2);        new Thread(new Runnable() {            @Override            public void run() {                System.out.println("线程1执行");                latch.countDown();            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                System.out.println("线程2执行");                latch.countDown();            }        }).start();        latch.await();        System.out.println("线程3执行");    }

初始化


初始化state值,当state值>0代表锁被占有,=0阐明锁被开释

await()




1.tryAcquireShared():state不等于0的时候,tryAcquireShared()返回的是-1,此时获取锁失败,也就是说count未减到0的时候所有调用await()办法的线程都要排队。
2.doAcquireSharedInterruptibly():创立一个Node节点绑定以后的线程,退出到一个FIFO的双向链表中,先判断以后传入的Node对应的前置节点是否为head节点,如果是则尝试加锁,如果加锁失败或者Node的前置节点不是head节点,用LockSupport.park()挂起以后线程。

countDown()



1.tryReleaseShared():开释锁,通过自旋的CAS操作对state-1,如果state=0,返回true执行doReleaseShared()
2.doReleaseShared():唤醒期待await()的线程

总结

独占模式流程:
1.tryRequire()办法尝试获取锁,具体通过CAS操作尝试批改state值,胜利则设置state值为1,且设置对象独占锁线程为以后线程
2.获取失败,创立一个Node节点绑定以后的线程,退出到一个FIFO的双向链表中
3.如果持有锁的线程应用tryRelease()开释了锁,state从新设置为0,独占线程设置为null,唤醒队列中的第一个Node节点中的线程再次争抢锁

共享模式流程:
1.tryRequireShared()办法尝试获取锁,具体通过判断以后state值,>0则代表获取锁失败,=0则获取锁胜利
2.获取失败,创立一个Node节点绑定以后的线程,退出到一个FIFO的双向链表中
3.如果持有锁的线程应用tryRelease()开释了锁,会state进行-1,当state=0时,唤醒队列中所有的Node节点中的线程

参考大佬:
我画了35张图就是为了让你深刻 AQS
从ReentrantLock的实现看AQS的原理及利用