深入理解AQS和ReentrantLock

目录

1. 管程

1.1 什么是管程

1.2 管程模型-MESA

2. AQS

2.1 什么是AQS

2.2 AQS的特性

2.3 AQS核心结构

2.3.1 AQS内部维护属性volatile int state

2.3.2 定义了两种资源访问方式

2.3.3 AQS实现时主要实现的方法

2.4 AQS定义两种队列

2.4.1 同步等待队列

2.4.2 条件等待队列

3. AQS的实现之 ReentrantLock 详解

3.1 ReentrantLock基本使用

3.2 ReentrantLock的主要源码解读

3.2.1 创建锁 new ReentrantLock()

3.2.2 获取锁:lock()

3.2.3 unlock()


1. 管程

1.1 什么是管程

管程:指的是管理共享变量以及对共享变量的操作过程,让他们支持并发
互斥:同一时刻只允许一个线程访问共享资源;
同步:线程之间如何通信、协作。

1.2 管程模型-MESA

        在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen模型、Hoare模型和MESA模
型。现在正在广泛使用的是 MESA模型
        管程中引入了条件变量的概念,而且每个条件变量都对应有一个等待队列条件变量和等待队列的 作用是解决线程之间的同步问题。

2. AQS

2.1 什么是AQS

        AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。

java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队 列、独占获取、共享获取等,而这些行为的抽象就是基于 AbstractQueuedSynchronizer(简称 AQS) 实现的。
JDK中提供的大多数的同步器如Lock, Latch, Barrier等,都是基于AQS框架来实现的 。
  1.         一般是通过一个内部类Sync继承 AQS
  2.         将同步器所有调用都映射到Sync对应的方法
以ReentrantLock为例:下图是ReentrantLock内部类Sync的继承关系

2.2 AQS的特性

  • 阻塞等待队列
  • 共享/独占
  • 公平/非公平
  • 可重入
  • 允许中断

2.3 AQS核心结构

2.3.1 AQS内部维护属性volatile int state

  1. state表示资源的可用状态
  2. State三种访问方式:
  • getState()
  • setState()
  • compareAndSetState()

2.3.2 定义了两种资源访问方式

  1. Exclusive-独占,只有一个线程能执行,如ReentrantLock
  2. Share-共享,多个线程可以同时执行,如Semaphore/CountDownLatch(可参考常用并发工具类的使用及应用场景详解-优快云博客 中的Semaphore和CountDownLatch进行理解)

2.3.3 AQS实现时主要实现的方法

  1. isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
  2. tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  3. tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  4. tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表
  5. 示成功,且有剩余资源。
  6. tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回 false。

2.4 AQS定义两种队列

  1. 同步等待队列: 主要用于维护获取锁失败时入队的线程。
  2. 条件等待队列: 调用await()的时候会释放锁,然后线程会加入到条件队列,调用signal()唤醒的时候会把条件队列中的线程节点移动到同步队列中,等待再次获得锁。
AQS 定义了5个队列中节点状态:
        1. 值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。
        2. CANCELLED,值为1,表示当前的线程被取消;
        3. SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
        4. CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
        5. PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;

2.4.1 同步等待队列

        AQS当中的同步等待队列也称CLH队列,CLH队列是Craig、Landin、Hagersten三人发明的一种 基于双向链表数据结构的队列 是FIFO先进先出线程等待队列 ,Java中的CLH队列是原CLH队列的一 个变种,线程由原自旋机制改为阻塞机制。
        AQS 依赖CLH同步队列来完成同步状态的管理:
  1. 当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程.
  2. 当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
  3. 通过signal或signalAll将条件队列中的节点转移到同步队列。(由条件队列转化为同步队列

2.4.2 条件等待队列

 AQS中条件队列是使用单向列表保存的,用nextWaiter来连接:

  • 调用await方法阻塞线程;
  • 当前线程存在于同步队列的头结点,调用await方法进行阻塞(从同步队列转化到条件队列

3. AQS的实现之 ReentrantLock 详解

        ReentrantLock是一种基于AQS框架的应用实现 ,是JDK中的一种线程并发访问的同步手段,它的 功能类似于synchronized 是一种互斥锁,可以保证线程安全

3.1 ReentrantLock基本使用

   public class ReentrantLockTest { 
            private final ReentrantLock lock = new ReentrantLock(); 
            // ... 34
            public void doSomething() { 
                lock.lock(); // block until condition holds 6
                try { 
                    // ... method body 8
                } finally { 
                    lock.unlock(); 1
                } 
            } 
        } 

3.2 ReentrantLock的主要源码解读

        AQS的特性有阻塞等待队列 、共享/独占 、公平/非公平 、可重入 、允许中断,那么我们解读ReentrantLock时着重看一下这几个特性在ReentrantLock中是如何实现的。

3.2.1 创建锁 new ReentrantLock()

ReentrantLock的锁可以是公平锁也可以是非公平锁

从下图中ReentrantLock的构造方法可知,创建锁有两种:

  • ReentrantLock() 非公平锁
  •  ReentrantLock(boolean fair)  公平锁

3.2.2 获取锁:lock()

非公平锁

如上述,如果ReentrantLock创建的是非公平锁,则实际创建的是他的内部类NonfairSync对象,则lock()调用的则是NonfairSync的lock()方法

compareAndSetState()返回true,执行setExclusiveOwnerThread(),其源码注释说明,CAS成功后,当前线程获取了独占锁

compareAndSetState()返回false, 则进入acquire(1); 该方法则是获取锁的一些相关操作。

我们看以下tryAcquire这个方法,当前要获锁线程和已经占用锁的线程是同一个锁时,不会死锁,而是给这个锁的状态加一,以下代码实现了AQS的可重入性这个特点

我们再看一下这个lock()中compareAndSetState()方法,其实就是一个原子操作,当前线程更改该state,更改成功,返回true.

公平锁

如上述,如果ReentrantLock创建的是公平锁,则实际创建的是他的内部类FairSync对象,则lock()调用的则是FairSync的lock()方法,这个lock直接调用了acquire()方法。

总结: 

        通过上述公平锁与非公平锁获取的阐述,我们发现非公平锁获取锁时,多了一步CAS去修改state,也就是说,当一个线程进来时,多了一次直接抢占到锁的机会。如果没有抢到,后续和公平锁一样,进入队列等待。

        当要获取锁的线程和占有锁是同一个线程时,状态值加1,实现了可重入性。

        每次只容许一个线程进入,是一个独占锁。

        当没有获取到锁,会被挂起。

3.2.3 unlock()

ReentrantLock的unlock方法实际调用的是AbstractQueuedSynchronizer已经定义好的release方法。

Sync继承了AbstractQueuedSynchronizer。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑜伽娃娃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值