21-AQS:基于FIFO等待队列的阻塞锁

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38331606/article/details/84621377

  1. AQS:基于FIFO等待队列的阻塞锁
    1. 用来做什么?

当一个线程请求一个共享资源时,如果资源处于空闲状态,则设置该线程为有效的工作线程,并设置该资源为锁定状态;当资源处于锁定状态时,需要设置其他请求的线程处于阻塞状态,并在共享资源被唤醒时,分配当前处于阻塞状态的线程使其工作。

    1. 如何实现的?

使用一个FIFO的双端队列,用于存放锁上阻塞状态的线程,同时使用voliatile int state表示当前锁的状态,使用Unsafe工具对原子性操作来对state进行修改。

即当一个线程尝试获取锁时,如果锁已经被占用,则将该线程以及等待状态等信息构造为一个Node节点,加入到同步队列的尾部,同时阻塞当前线程。当队列顶部的线程节点,在占用资源完成后,释放资源,同时唤醒后面的节点并释放当前节点的引用。

    1. 设计思想

AQS,抽象同步队列。基于模板模式的设计,即其实现类通过重写指定的方法,同时调用父类特定的方法,从而达到调用子类重写的方法的目的。

    1. 类关系图

使用CAS设置当前状态;如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值。

      1. transient volatile Node head;

通过懒加载生成的,处于等待锁的队列的头、尾节点。

除了初始化,该头节点只能通过setHead()方法进行修改。

如果头节点存在,则头节点的waitStatus 属性不能被设置为CANCELLED状态(线程已取消)

      1. transient volatile Node tail;

通过懒加载方式生成的,处于等待锁的队列的尾节点;只支持add方式的编辑。

      1. 添加释放锁方法final

AQS仅仅只是提供独占锁和共享锁两种方式,但是每种方式都有响应中断和不响应中断的区别。    

其内部的方法均为空的实现,由子类实现,而子类通常被称为自定义同步装置的内部类。

  • 不响应中断的独占锁:acquire(int arg)
  • 响应中断的独占锁:acquireInterruptibly(int arg)
  • 不响应中断的共享锁:acquireShared(int arg)
  • 响应中断的共享锁:acquireSharedInterruptibly(int arg)

      1. isHeldExclusively()

当前同步器是否是在独占模式下的调用

CANCELLED:1,代表线程已经取消。

当线程等待超时或者被中断时,需要从之前的等待状态设置为取消等待状态;

SIGNAL:-1,该线程后面的线程需要启动。

当前节点释放同步状态或者被取消时,需要通知给后继节点;

CONDITION:-2,线程正在等待condition。

该节点当前存在在等待队列Condition上,当其他线程对Condition调用signal()方法后,需要将该节点共等待队列转移到同步队列中,以获取同步状态。

PROPAGATE,值为-3,下一次的共享状态会被无条件的传播下去;

INITIAL:0,初始状态,节点在同步队列中等待获取锁。

  • Node prev:前驱节点    Node next:后继节点
  • Node nextWaiter:下一个等待condition的node
  • Thread thread:获取同步状态的线程
  • Node SHARED = new Node():当前是共享节点; Node EXCLUSIVE = null:独占节点
    1. 自定义锁源码实现

AbstractQueuedSynchronizer是一个抽象类,在多线程体系中是底层的实现;其子类(锁),通过继承的方式,重写AQS内部的方法(置空方法),从而实现自身的业务。

    1. AQS中状态的变更

AQS全称AbstractQueuedSynchronizer,java.util.concurrent.locks包中一个抽象类,含一个用于存储被阻塞的线程的队列,以及一个表示对应线程等待状态的waitStatus值。

    1. 头节点,尾节点设置,waitStatus的更新

 

    1. AQS.acquire源码分析

当线程t调用自定义的Lock.lock()方法时,主要执行以下逻辑:

  1. AQS.acqurie()方法,调用子类同步器重写的tryacquire()方法,尝试获取锁,并且成功;

设置当前线程为AOS的工作线程,并设置AQS同步器的state为占用状态。

  1. 获取锁失败后,通过acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,添加该线程节点到FIFO队列中;并对FIFO队列清除非法节点,完成后阻塞当前线程,LockSupport.park(this);
  2. 当其他线程通过release方法释放锁,并唤醒该线程t后,线程t启动,并尝试获取锁,直至获取成功;
  3. 设置当前线程t所在的节点为队列的head节点

    1. AQS.release源码分析

当线程t2执行完成后,调用自定义锁的Lock.unlock()方法时,主要执行以下逻辑:

  1. 调用子类重写的AQS方法Lock.Sync.tryRelease(1),用于设置AQS的state为0,设置AOS的工作线程为null;
  2. 在head.waitStatus != 0的情况下(即存在节点在队列中尝试获取锁),从尾节点开始便利获取靠近头节点最近的合法节点s(s.waitStatus <0);
  3. 唤醒节点s的线程LockSupport.unpark(s.thread);

 

    1. AQS.acquire----AQS.release流程运转

 

 

 

多线程学习大纲:https://mp.csdn.net/postedit/84768644

猜你喜欢

转载自blog.csdn.net/qq_38331606/article/details/84621377