JAVA劝退系列AQS源码阅读

前言

从今天开始连续几篇进行AQS底层分析,看源码,本篇是开篇

   Java并发编程核心在于java.concurrent.util包而juc当中的大多数同步器实现都是围绕着共同的基础行为,
比如等待队列、条件队列、独占获取、共享获取等,
   而这个行为的抽象就是基于AbstractQueuedSynchronizer简称AQS,
   AQS定义了一套多线程访问共享资源 的同步器框架,是一个依赖状态(state)的同步器

ReentrantLock整体流程

//使用ReentrantLock进行同步 //false为非公平锁, true为公平锁 
 ReentrantLock lock = new ReentrantLock(false)
 lock.lock() //加锁 
 业务逻辑
 lock.unlock() //解锁
我们分析下这个lock的过程

lock.lock() //加锁
    xxxx业务逻辑
    xxxxx业务逻辑 
lock.unlock() //解锁

假设有T0,T1,T2三个线程,拿到锁的T0将会执行业务逻辑,而没拿到锁的T1,T2将在lock方法里,不可能下来,那怎么可以用自选的方式,让T1,T2一直在while循环里

 lock.lock() //加锁
 while(true){
      if(cas加锁成功){//cas->比较与交换compare and swap,
          break;跳出循环
      }
 }
 xxxx业务逻辑
 xxxxx业务逻辑
 lock.unlock() //解锁

假设有很多线程,都在while循环里去跑,会浪费CPU,所以可以

 lock.lock() //加锁
 while(true){
      if(cas加锁成功){//cas->比较与交换compare and swap,
          break;跳出循环
      }
      Thread.yeild()//让出CPU使用权
      Thread.sleep(1);
 }
 xxxx业务逻辑
 xxxxx业务逻辑
 lock.unlock() //解锁

但是如果T0在下面执行的业务逻辑时间很长,不可能让CPU一直处于礼让状态。所以可以睡眠一下,睡眠多久合适呢,需要判断睡眠多久合适。但是这个时间不好控制,所以就有了采取阻塞的方式。

 lock.lock() //加锁
 while(true){
      if(cas加锁成功){//cas->比较与交换compare and swap,
          break;跳出循环
      }
      //Thread.yeild()//让出CPU使用权
      //Thread.sleep(1);
      阻塞。
     LockSupport.park();
 }
 xxxx业务逻辑
 xxxxx业务逻辑
 lock.unlock() //解锁

采取阻塞,让线程停在这里下不来。但是不能一直阻塞在这里。当T0解锁了,需要唤醒

lock.lock() //加锁
 while(true){
      if(cas加锁成功){//cas->比较与交换compare and swap,
          break;跳出循环
      }
      //Thread.yeild()//让出CPU使用权
      //Thread.sleep(1);
      阻塞。
     LockSupport.park();
 }
 xxxx业务逻辑
 xxxxx业务逻辑
 lock.unlock() //解锁
 LockSupport.unpark(线程);

那么线程是哪里来的呢,比如用HashSet,LikedQueued进行存储,在T0解锁后就可以从队列或者set里拿出来。

lock.lock() //加锁
while(true){
    if(cas加锁成功){//cas->比较与交换compare and swap,
        break;跳出循环
    }
    //Thread.yeild()//让出CPU使用权
    //Thread.sleep(1);
    HashSet,LikedQueued(),
    HashSet.add(Thread)
        LikedQueued.put(Thread)
    阻塞。
    LockSupport.park();      
}
T0获取锁
xxxx业务逻辑
xxxxx业务逻辑    
lock.unlock() //解锁
Thread  t= HashSet.get()
Thread  t = LikedQueued.take();
LockSupport.unpark(t);

当T0完成后,唤醒T1,T1进行自旋,当加锁成功后跳出循环,执行业务逻辑。T2一样

Lock三大核心原理

自旋,LocksSuport, CAS(加锁,不管有多少线程来,永远要保证只有1个线程能加锁成功),
数据结构采用:queue队列,原因:要想实现公平和非公平,需要排队,FIFO。就保证了公平性。

CAS

假设主内存expect值是0,我们有俩个线程要修改这个值。
在这里插入图片描述

俩个线程都读一份,更新的值refresh,A是1,B是2,假设这俩个线程都要去修改主内存的值

假设是线程B先去修改主内存值,会拿线程B expect和主内存里的expect去做比对,
如果相等,就把refresh值写到主内存的expect里,不相等不能修改。
此时线程A比较发现不相等,不能修改,如果修改需要把主线程的值(此时变为1)
再次读取,下次修改的时候进行比较交换。

在这里插入图片描述

ReentrantLock底层源码分析

ReentrantLock如何实现synchronized不具备的公平与非公平性呢?

在ReentrantLock内部定义了一个Sync的内部类,该类继承AbstractQueuedSynchronized,对该抽象类的部分方法做了实现;并且还定义了两个子类:
1、FairSync 公平锁的实现
2、NonfairSync 非公平锁的实现 这两个类都继承自Sync,也就是间接继承了AbstractQueuedSynchronized,所以这一个 ReentrantLock同时具备公平与非公平特性。

先看下类图,选中AbstractQueuedSynchronizer,按住TRL+t,出现后全选回车
在这里插入图片描述
在这里插入图片描述
公平锁与非公平锁的代码区分
在这里插入图片描述
当前获取线程是谁
在这里插入图片描述
锁被谁记录,拿了多少次。当state为0表示当前锁没被任何人持有,可以进行加锁
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Node里有个对线程引用的变量,基于这个引用去unpark唤醒
在这里插入图片描述
核心的属性都介绍了,以公平锁为例简单看下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下图tryAcquire方法,尝试去获取锁,首先把当前线程引用拿出来,然后查看state
在这里插入图片描述
在这里插入图片描述
c==0不是一上来就可以去取,因为会有排队,所以需要!hasQueuedPredecessors(),怎么判断队列有没有线程,判断对头head和队尾tail是不是null。是就是空队列
队列没有等待的,然后CAS,将state值由0>1。然后
在这里插入图片描述
在这里插入图片描述
第2个判断,不存在并发
在这里插入图片描述
如果tryAcquire尝试获取锁失败,例如T2,T3则进行acquireQuened方法。
在这里插入图片描述

selfInterrupt 程序员自己定义的代码也要识别到中断信号,中断信号往外面的代码去传。这个在哪有用呢?

public void lockInterruptibly() throws InterruptedException {
   sync.acquireInterruptibly(1);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值