JUC框架--AQS
java.util.concurrent包的核心类是AbstractQueueSynchronizer。
AQS队列同步器
AQS是一个同步器+阻塞锁的基本架构,用于控制加锁和释放锁,并在内部维护一个FIFO(First In First Out,先进先出)的线程等待队列。
java.util.concurrent包中的锁、屏障等同步器多数都是基于它实现的。
AQS相关类的关系图:
-
AbstractOwnableSynchronizer
是一个可以由线程以独占方式拥有的同步器,这个类为创建锁和相关同步器提供了基础。 -
AbstractQueuedSynchronizer
用虚拟队列的方式来管理线程中锁的获取与释放,同时也提供了各种情况下的线程中断。这个类虽然提供了默认的同步实现,但是获取锁和释放锁的实现被定义为抽象方法,由子类实现。
这样做的目的是使开发人员可以自由定义锁的获取与释放方式。
-
Sync
是ReentrantLock的内部抽象类,实现了简单的锁的获取与释放。Sync有两个子类NonfairSync和FairSync,分别表示“非公平锁”和“公平锁”(图中并未给出),且都是ReentrantLock的内部类。 -
ReentrantLock
实现了Lock接口的lock-unlock方法,这个方法会根据fair参数决定使用NonfairSync还是FairSync。 -
ReentrantReadWriteLock
是Lock的另一种实现方式,ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。与排他锁相比,能提供更高的并发性。
AQS是Java concurrency容器的重要基础。
1. AQS的同步状态关键字
state是AQS非常重要的描述线程同步状态的成员变量
private volatile int state;
state用来表示“线程争抢的资源”。如果是0,那么说明没有线程正在等待资源,如果为n(n>0),那么说明有n个线程正在等待资源释放。
在AQS中,state有三种访问方式:getState、setState、compareAndSetState。
volatile关键字让state变量保持线程间的可见性和有序性,是保证state线程安全的必要条件之一。
AQS定义两种资源共享方式
(1)Exclusive(独占,在特定的时间内,只有一个线程能执行,如ReentrantLock)
(2)Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
自定义同步器时主要需要实现的方法:
1)isHeldExclusively():该线程是否正在独占资源。只有用到condition时才需要去实现它。
2)tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
3)tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
4)tryAcquireShared(int):共享方式。尝试获取资源。成功返回0,失败返回负数。
5)tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点,那么返回true,否则返回false。
2. volatile关键字
volatile的使用是为了线程安全,但volatile不保证线程安全。
线程安全是指在多线程情况下,对共享内存的使用,不会因为不同线程的访问和修改而发生不期望的情况。
线程安全有三个要素:可见性,有序性,原子性。
- 可见性是指当多个线程在访问同一个变量时,一个线程修改了变量的值,其他线程应该能立即看到修改后的值。
- 有序性:程序执行的顺序应当按照代码的先后顺序执行。
- 原子性:一个或多个操作,要么全部连续执行且不会被任何因素中断,要么就都不执行。
volatile有三个作用:
(1)volatile用于解决多核CPU高速缓存导致的变量不同步
这本质上是个硬件问题,其根源在于:CPU的高速缓存的读取速度远远快于主存(物理内存)。
(2)volatile可以解决指令重排序的问题
被volatile修饰的变量,CPU不会对它做重排序优化,所以也就保证了有序性。
(3)volatile不保证操作的原子性
volatile保证可见性和有序性,但是不保证操作的原子性。
3. AQS和CAS
Compare And Swap(之后简称CAS)从字面上理解是“比较并交换”的意思,它的作用是,对指定内存地址的数据,校验它的值是否为期望值,如果是,那么修改为新值,返回值表示是否修改成功。
AQS使用了volatile以保证head和tail结点执行中的有序性和可见性,又使用了unsafe/CAS来保障了操作过程中的原子性。
AQS的结点操作满足线程安全的三要素,可以认为相关操作是线程安全的。
CAS的执行方式是自旋锁,与synchronized相比,更加充分利用了资源,效率更高。
4. AQS的等待队列
AQS的原理在于,每当有新的线程请求资源的时候,该线程都会进入一个等待队列(Waiter Queue),只有当持有锁的线程释放资源之后,该线程才能持有资源。
该等待队列的实现方式是双向链表,线程会被包裹在链表结点Node中。
(1)结点对象Node
Node即是队列的结点对象,它封装了各种等待状态,前驱与后继结点信息,以及它对应的线程。
成员变量
// 标记结点为共享模式
static final Node SHARED = new Node();
// 标记结点为独占模式
static final Node EXCLUSIVE = null;
// 等待状态
volatile int waitStatus;
// 前驱结点
volatile Node prev;
// 后继结点
volatile Node next;
// 线程
volatile Thread thread;
等待状态
// 表示线程已取消
static final int CANCELLED = 1;
// 表示竞争锁的胜者线程需要唤醒(使用LockSupport.unpark)
static final int SIGNAL = -1;
// 表示线程正在condition队列中等待
static final int CONDITION = -2;
//表示后继结点会传播唤醒操作,只会在共享模式下起作用
static final int PROPAGATE = -3;
(2)独占模式和共享模式
独占模式表示该锁会被一个线程占用着,其他线程必须等到持有线程释放锁后才能获取到锁继续执行,也就是说,在同一时间内,只能有一个线程获取到这个锁。在Concurrency包里,ReentrantLock就是采用的独占模式。
共享模式表示多个线程获取同一个锁的时候,有可能(并非一定)会成功,在Concurrency包里,ReadLock采用的这种模式。
在AQS中,独占模式和共享模式的方法实现是不一样的,它们的方法成对出现。
(3)等待队列的状态处理
AQS的acquire()方法的实现代码:
public final void acquire(int arg){
// addWaiter() 封装当前线程为等待队列结点并插入到队列中。
// acquireQueued(Node) 会接收addWaiter()封装好的Node对象。本质在于以自旋的方式获取资源,即自旋锁。
if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE),arg)){
selfInterrupt();
}
}
acquire方法用于获得锁,该方法执行流程如下:
1)尝试获取锁(tryAcquire),tryAcquire的返回值表示当前线程是否成功获取锁。
2)如果获取成功,那么说明当前对象已经持有锁,执行中断操作,中断操作会解除线程阻塞。
3)如果获取失败,那么把当前线程封装为Waiter结点,封装的过程已经把该结点添加进Waiter队列。
4)acquiredQueue自旋获取资源,并且返回该Waiter结点持有的线程的应当具备的中断状态。
5)根据返回结果来确定是否需要执行线程的中断操作。
5. AQS阻塞线程和中断阻塞
线程阻塞有三种常见的实现方式:Object.wait()、Thread.join()或者Thread.sleep()。
中断阻塞通过Thread.interrupt()方法来实现。
如果线程被Object.wait(),那么Thread.join()或者Thread.sleep()方法阻塞,可以通过调用线程的interrupt()方法来中断阻塞,这个方法会发出一个中断信号量从而导致线程抛出中断异常InterruptedException,以达到结束阻塞的目的。
Interrupt不会中断用户用循环体造成阻塞,它仅仅只是抛出信号量,具体的处理方式还是得由用户处理。Thread.isInterrupted方法可以得到中断状态。
6. sun.misc.Unsafe
破坏了Java的安全性,它提供的方法可以直接操作内存和线程,一旦不慎,可能会导致严重的后果。
功能:
①获取底层内存信息,比如addressSize()、pageSize()。
②获取类相关的信息,比如分配实例内存allocateInstance(Class),获取静态域偏移量staticFieldOffset(Field)。
③数组相关,比如获取索引范围arrayIndexScale(Class)。
④同步相关,比如挂起线程park,CAS操作compareAndSwapInt。
⑤直接访问内存,比如申请内存allocateMemory(long)。