并发编程AQS(AbstractQueueSynchronize)源码理解

  1. AQS(AbstractQueueSynchronize)并发编程同步器,这是我们平时使用到的ReentryLock等锁的核心类;
    1. 使用到了模板设计模式,笔记核心的一些方法或者设计都在这里面实现了;子类只需要实现几个特定的方法;
    2. 如果说我们需要实现自己的锁(独占锁),我们锁的内部定义一个sync类继承自AQS,然后实现tryAcruire和tryRelease方法;
    3. AQS的基本思想:CLH队列锁;
      1. 在AQS中有一个双向的阻塞队列,如果说我们我们的一个线程想要获取锁,先将这个线程放入到这个双向队列的末尾,然后这里面一个核心就是这个队列中的每一个节点都会进行自旋(CAS),前一个节点是否释放锁,如果释放锁,就进入阻塞状态;

 

  1. AQS的源码理解:(以ReentrantLock这个类中的非公平锁NonfairSync为例子)

AQS源码中主要关注这几个变量:state(线程的状态)

state表示当前锁的状态

state=0:表示无锁状态;

state>0:表示有线程获取了锁,也就是state=1,如果同一个线程多次获得同步锁,state会递增,每获得一次锁,state加一,而在释放锁的时候,同样需要释放5次知道state=0,其他线程才有资格获取锁;

state还有一个功能是实现锁的独占模式和共享模式:

  • 独占模式:只有一个线程能持有同步锁;在独占模式下,我们可以吧state的初始值设置成为0;当某个线程申请锁对象的时候,需要判断state是否为0,如果不为0,以为这其他线程已经持有了这个锁,则本线程需要等待;
  • 共享模式:可以有多个线程持有同步锁;在共享模式下的道理也是差不多的,比如我们设置某项操作我们允许有10个线程同时进行,超过这个数量的线程就需要阻塞等待。那么只需要在线程申请锁对象的时候,判断state的值是否小于10,如果小于10,就将state加1后继续执行同步语句,如果等于10,就说明已经有10个线程在同时执行该操作,本线程需要阻塞等待;
  1. Lock方法:

在进一步进去看acquire方法的具体实现:

其中可以看到tryAcquire方法是直接抛出异常,也就是这个方法必须要有子类去实现:

然后我们再去ReentrantLock中的NonfairSync对于tryAcquire方法的具体实现:

继续:

使用一张图表示ReentrantLock.lock()的过程:

 

从上图中我们可以看出来,大部分的逻辑实现都在AQS内部封装实现好了,ReentranLock自身只需要实现某些特定的步骤下的方法即可;这种设计模式叫做模板设计模式(Activity中的生命周期就是这种设计模式);

 

  1. AQS的核心功能的原理分析:

AQS中除了我上面讲到的state变量重要意外,还有一个变量Node,如下可以看到Node中的成员变量:

Node是一个先进先出的双端队列,并且是等待队列;当多线程争取锁的阻塞的时候,会进入到这个队列中,这个队列是AQS实现多线程同步的核心;默认情况下,AQS中的链表结构如下:

我们知道竞争失败的线程需要被阻塞等待,那么ReentrantLock是如何实现让线程等待并唤醒的呢?

前面中我们提到在 ReentrantLock.lock() 阶段,在 acquire() 方法中会先后调用 tryAcquire、addWaiter、acquireQueued 这 3 个方法来处理。tryAcquire 在 ReentrantLock 中被复写并实现,如果返回 true 说明成功获取锁,就继续执行同步代码语句。可是如果 tryAcquire 返回 false,也就是当前锁对象被其他线程所持有,那么当前线程会被 AQS 如何处理呢?

根据上面的源码分析我们知道在tryAcquire方法调用返回false以后(获取锁失败的情况下),会继续调用addWaiter方法,然后调用acquireQueued()方法;

addWaiter方法,将该节点加入到等待队列中去;

  • 通过CAS指令加入到队列尾部中去;
  • 如果加入失败调用enq方法;

对于enq方法;

  • 如果该队列还没有进行初始化,先初始化该队列;
  • 如果队列已经初始化了,将该节点通过CAS指令放到队列的结尾;

当我们把该包裹等待获取锁的线程放入到线程中去以后;最后一个步骤就是,将该线程阻塞,那么是如何进行阻塞的呢?继续看下面这个函数;

acquireQueued方法:

  • 取出node节点的前一个节点,如果该节点是头结点,则尝试获取锁;如果获取锁成功,将该节点出队;将node节点设置为头结点;
  • 如果node节点不为头结点,将该节点中的线程阻塞,具体的阻塞方法如下所示;

 

以上的步骤的总结:

  • AQS 的模板方法 acquire 通过调用子类自定义实现的 tryAcquire 获取锁;
  • 如果获取锁失败,通过 addWaiter 方法将线程构造成 Node 节点插入到同步队列队尾;
  • 在 acquirQueued 方法中以自旋的方法尝试获取锁,如果失败则判断是否需要将当前线程阻塞,如果需要阻塞则最终执行 LockSupport(Unsafe) 中的 native API 来实现线程阻塞。

 

 

  1. JMM(Java  memory model)Java
  2. 的内存模型:

解释:我们的运行的Java程序有一个主内存和每一个单独的线程中的工作内存的概念;假如有两个线程使用到了主内存之中的一个变量,首先是先从主内存读取这个这个值加载到该线程中的工作内存,然后在对这个值进行自己的业务逻辑运算,在进行运算以后,我们在把这个值写回主内存;这是一个单线程的运行概念,但是当有多个线程在共享一个变量的时候,不可避免的会出现一些问题,比如原子性,和可见性;

 

volatile这个关键字可以保证这个变量的可见性,因为只要我们对一个变量加上了这个关键字,那么我们线程中每次使用这个变量的时候,我们都必须从主内存中取,然后当我们每次使用完这个变量时候,我们都需要将该变量在线程工作内存的值刷回主内存,这个关键字保证了可见性;但是这个关键字不能保证原子性,在多线程的情况下,我们多个线程共同持有一个变量的情况下,不能保证原子性(需要加上synchronize关键字);

volatile的原理:CPU提供的lock前缀的指令;

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值