JUC框架--AQS

java.util.concurrent包的核心类是AbstractQueueSynchronizer。

AQS队列同步器

AQS是一个同步器+阻塞锁的基本架构,用于控制加锁和释放锁,并在内部维护一个FIFO(First In First Out,先进先出)的线程等待队列。

java.util.concurrent包中的锁、屏障等同步器多数都是基于它实现的。

AQS相关类的关系图:
AQS关系图

  1. AbstractOwnableSynchronizer
    是一个可以由线程以独占方式拥有的同步器,这个类为创建锁和相关同步器提供了基础。

  2. AbstractQueuedSynchronizer
    用虚拟队列的方式来管理线程中锁的获取与释放,同时也提供了各种情况下的线程中断。这个类虽然提供了默认的同步实现,但是获取锁和释放锁的实现被定义为抽象方法,由子类实现。

这样做的目的是使开发人员可以自由定义锁的获取与释放方式。

  1. Sync
    是ReentrantLock的内部抽象类,实现了简单的锁的获取与释放。Sync有两个子类NonfairSync和FairSync,分别表示“非公平锁”和“公平锁”(图中并未给出),且都是ReentrantLock的内部类。

  2. ReentrantLock
    实现了Lock接口的lock-unlock方法,这个方法会根据fair参数决定使用NonfairSync还是FairSync。

  3. 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不保证线程安全。

线程安全是指在多线程情况下,对共享内存的使用,不会因为不同线程的访问和修改而发生不期望的情况。

线程安全有三个要素:可见性,有序性,原子性。

  1. 可见性是指当多个线程在访问同一个变量时,一个线程修改了变量的值,其他线程应该能立即看到修改后的值。
  2. 有序性:程序执行的顺序应当按照代码的先后顺序执行。
  3. 原子性:一个或多个操作,要么全部连续执行且不会被任何因素中断,要么就都不执行。

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)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值