AQS 原理和 ReentrantLock 源码(上)

简介: 本文中采用的 jdk 版本为 openjdk-1.8

说明


  1. 本文中采用的 jdk 版本为 openjdk-1.8


锁和线程状修改


  1. 加锁的本质?主要是为了在访问临界资源的时候,能够实现一个等待唤醒得有序操作。


  1. Java 中的锁的分类: sychronizedLock 。在 sychronized 中主要是通过 monitor 来进行实现的。通过 Object.wart/notify 实现线程得阻塞和唤醒; 第二种就是基于线程的 LockSupport.park/unpack 阻塞和唤醒。


  1. 线程的中断问题,如何优雅的中断一个线程?


  • Java中断机制是一种协作机制,也就是说中断并不能直接终止某一个线程,而需要被中断的线程自己处理中断


  • API 的使用:
    interrupt(): 将线程的中断标示位设置为 true;
    isInterrupted(): 判断当前线程的中断标志位是否是 true;
    Thread.interrupted(): 判断当前线程中断位置是否位 true, 并且清除中断标志位,重置为 fasle。


  1. LockSupport 会造成线程中断吗? LockSupport  不会造成线程中断的。


  1. CAS 是什么?比较交换, 主要是一个乐观锁的概念, 底层采用的是 unsafe api 来实现比较交换。


protected final boolean compareAndSetState(int expect, int update) {    
    // See below for intrinsics setup to support this    
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}


AQS 的原理和实现


AQS 是基于队列在实现排队 AbstractQueuedSynchronizer 类为模板方法的实现。


  1. 底层数据结构是一个双向链表。每个 Node 节点包含 prev,和 next 指针,以及数据数据字段,这里的数据字段用就是一个线程 Thread 对象。


  1. Node 的四种状态: 取消,等待,条件等待,共享状态


  1. 通常的两种实现:公平锁,非公平锁。 怎么提现公平指的是同一个时刻新加入的数据,和队列头的数据竞争是否能够进行公平的资源竞争。


  1. 状态的修改通过 CAS 来实现,底层是调用 sun.misc.Unsafe 的 compareAndSwapInt 进行状态的修改。


  1. 在线程进入队列之前会进行尝试加锁,如果拿不到锁会阻塞当前线程并且线程通过 LockSupport.park() 进入阻塞。


  1. 释放锁的时候,就会去队列中拿队列头的节点,进行唤醒,同步队列的头节点 head 就是当前获取锁的线程


  1. 下面是获取锁中 AQS 的核心代码


     a.  尝试加锁,如果加锁不成功,就进入队列进行重试。


// 独占的方式获取锁, 可以忽略中断, 最少调用一次,如果失败会进行排队,直到成功
 public final void acquire(int arg) {
     if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
 }


    b.  队列中,先去判断是否是队列头,如果是去尝试加锁,如果不是就对 Node 的状态进行修改,修改为等待唤醒。状态修改成功后把线程状态修改为阻塞。


final boolean acquireQueued(final Node node, int arg) {
     boolean failed = true;
     try {
         boolean interrupted = false;
         for (;;) {
             final Node p = node.predecessor();
             if (p == head && tryAcquire(arg)) {
                 setHead(node);
                 p.next = null; // help GC
                 failed = false;
                 return interrupted;
             }
             if (shouldParkAfterFailedAcquire(p, node) &&
                 parkAndCheckInterrupt())
                 interrupted = true;
         }
     } finally {
         if (failed)
             cancelAcquire(node);
     }
 }


下面是 shouldParkAfterFailedAcquire 方法对 Node 的状态进行维护。


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
     int ws = pred.waitStatus; // 前继节点的状态, 第一次进入默认值 0
     if (ws == Node.SIGNAL)
         return true;
     if (ws > 0) {
         do {
             // 出队
             node.prev = pred = pred.prev;
         } while (pred.waitStatus > 0);
         pred.next = node;
     } else {
         // 第一次进来, pred.waitStatus = 0 执行这个分支
         // 将前继节点的状态修改为 SIGNAL, 表示 pred.next 节点需要被唤醒(此时准备进入阻塞, 但是还未被阻塞, 再次获取锁失败之后才会被阻塞)
         compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
     }
     return false;
 }


AQS 流程图


image.png


ASQ 特征


  1. 阻塞等待队列


  1. 共享独占


  1. 公平/非公平


  1. 可重入


  1. 允许中断



相关文章
【ReentrantReadWriteLock的实现原理】
【ReentrantReadWriteLock的实现原理】
|
6月前
|
Java
ReentrantLock(可重入锁)源码解读与使用
ReentrantLock(可重入锁)源码解读与使用
|
6月前
|
Java
从源码入手详解ReentrantLock,一个比synchronized更强大的可重入锁
【5月更文挑战第6天】从源码入手详解ReentrantLock,一个比synchronized更强大的可重入锁
36 1
|
6月前
|
安全 Java
ReentrantLock 原理你都知道吗?
通过以上步骤和示例代码,你应该对 ReentrantLock 的工作原理有了清晰的理解。欢迎关注威哥爱编程,一起学习成长。
|
Java 程序员 API
AQS 原理解读
AQS 原理解读
|
安全
AQS学习:ReentrantLock源码解析
AQS学习:ReentrantLock源码解析
50 0
AQS(abstractQueuedSynchronizer)锁实现原理详解
AQS(abstractQueuedSynchronizer)抽象队列同步器。其本身是一个抽象类,提供lock锁的实现。聚合大量的锁机制实现的共用方法。
151 0
|
机器学习/深度学习 Java 调度
浅谈AQS原理
经典八股文之AQS原理
196 0
|
存储 Java
Java并发之AQS源码分析(一)
AQS 全称是 AbstractQueuedSynchronizer,顾名思义,是一个用来构建锁和同步器的框架,它底层用了 CAS 技术来保证操作的原子性,同时利用 FIFO 队列实现线程间的锁竞争,将基础的同步相关抽象细节放在 AQS,这也是 ReentrantLock、CountDownLatch 等同步工具实现同步的底层实现机制。它能够成为实现大部分同步需求的基础,也是 J.U.C 并发包同步的核心基础组件。
124 0
Java并发之AQS源码分析(一)