008 - Java并发中加锁原理和乐观锁

本片博文 参考 “深入理解CAS算法原理” ,更多细节请查阅原博客。 添加一些个人理解,如果理解有误,请提出宝贵意见

1. Java中锁的基本介绍
对象和类的锁
  • 只要是共享区域,就需要处理同步问题,下边是基本流程
    • 虚拟机给每个对象和类都分配一个锁
    • 同一时刻,只有一个线程可以拥有 这个类和对象
    • 如果一个线程想要获得某个类或者对象的锁,需要询问虚拟机
    • 当线程不再需要锁时,他再把锁还给虚拟机,这样虚拟机可以把锁再分配给其他申请锁的线程
  • 对象锁和类锁
    • 类锁其实通过对象锁实现的,当虚拟机加载一个类的时候,会会为这个类实例化一个 java.lang.Class 对象
    • 锁类时,实际上是锁住的Class对象
1.2 监视器 (Monitors)
  • 监视器和锁同时被JVM使用,监视器主要功能是监控一段代码,确保在同一时间只有一个线程执行
  • 每个监视器都与一个对象相关联,
    • 当线程执行到监视器监视下的代码块中第一条指令,线程必须获取对被引用对象锁定
    • 获取锁之前,无法执行这段代码
1.3多次加锁
  • 同一个线程可以对同一个对象进行多次加锁,
  • 每个对象维护着一个记录着被锁次数的计数器 : 同步代码块
    • 未被锁定的对象的该计数器为0
    • 当一个线程获得锁后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增
    • 当同一个线程释放锁的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁
1.4 同步
  • Java语言提供了两种内置方式来使线程同步的访问数据:同步代码块和同步方法

  • synchronized 可以对代码块和对象加锁

    synchronized (this) 对代码块加锁,同一时间只有一个线程进入该段代码,其他线程类的线程不受影响

    synchronized(objA) 对objA对象加锁,同一时间只有一个获得该锁的线程进入代码,其他的线程均处于阻塞状态,比如妻子消费者和儿子消费者都对家庭加了锁,那么同一时间只有一个人可以进行消费

2 . 乐观锁和CAS
  • Java中乐观锁和悲观锁

    JDK5之前,java用synchronized锁来控制同步问题 ; JDK5之后,增加了并发包java.util.concurrent.* , 其中的一些原子操作类(Atomic开头)采用的CAS算法就是一种乐观锁

  • 什么是CAS

    CAS就是 “Compare And Swap” , 先比较在交换 。 CAS是一种无锁算法 , 基本原理如下

    CAS有三个操作数,内存值V、旧的预期值、要修改的新值B。当且仅当 预期值A和内存值V相同时,将内存值V修改为B

  • CAS的理解

    CAS比较交换的伪码可以表示为下

    do{
    	备份旧数据; 
      基于旧数据构造新数据;
    }while(!CAS(内存地址,备份的旧数据,新数据))
    

    简单举例 :

    设存在两个线程 t1 、t2 , 同时去修改内存中的 一个变量56 ; 按照线程执行的规则,它们分别把56copy到自己的工作内存,修改,然后刷新回主存; 设t1 将值修改为57,并写回到主存中,对于t2来说,预期值是56但是内存中值成了57,内存值与预期值不一样,修改失败

    通俗解释:

    内存值和预期值进行比较时,如果相等就表示共享数据没有被修改,就可以替换新值继续向下运行,如果不相等,那说明共享数据已经被修改了,就放弃已经做得操作,然后重新执行刚才的操作

    CAS 操作是基于共享数据不会被修改的假设,当同步冲突出现机会很少时,这种假设会带来很大的性能提升

  • CAS开销

    CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快。CAS避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了

    Cache Miss:

    我们知道,CPU的硬件结构中 ,多喝处理器 存在多个CPU, 每个CPU又会存在缓存,管芯内还带有一个互联模块,使管芯内的两个核可以互相通信,数据以“缓存线”为单位在系统中传输。如下图所示

    [外链图片转存失败(img-CwqMvhJ3-1567494517501)(…/img/17.png)]

    CAS算法出现cache miss:

    设 CPU0 在对一个变量执行“比较并交换”(CAS)操作,而该变量所在的缓存线在 CPU7 的高速缓存中,就会发生以下经过简化的事件序列

    1. CPU0 检查本地高速缓存,没有找到缓存线

    2. 请求被转发到 CPU0 和 CPU1 的互联模块(interconnect),检查 CPU1 的本地高速缓存,没有找到缓存线。

    3. 请求被转发到系统互联模块(System Interconnect),检查其他三个管芯,得知缓存线被 CPU6和 CPU7 所在的管芯持有

    4. 请求被转发到 CPU6 和 CPU7 的互联模块,检查这两个 CPU 的高速缓存,在 CPU7 的高速缓存中找到缓存线

    5. CPU7 将缓存线发送给所属的互联模块,并且刷新自己高速缓存中的缓存线

    6. CPU6 和 CPU7 的互联模块将缓存线发送给系统互联模块

    7. 系统互联模块将缓存线发送给 CPU0 和 CPU1 的互联模块

    8. CPU0 和 CPU1 的互联模块将缓存线发送给 CPU0 的高速缓存

    9. CPU0 现在可以对高速缓存中的变量执行 CAS 操作

    CAS开销:

    最好的情况下(对某一个变量执行 CAS 操作的 CPU 正好是最后一个操作该变量的CPU,所以对应的缓存线已经在 CPU 的高速缓存中了), 消耗大约40纳秒 。最好情况下锁操作(一个“round trip 对”包括获取锁和随后的释放锁)消耗超过 60 纳秒,超过 100 个时钟周期。这里的“最好情况”意味着用于表示锁的数据结构已经在获取和释放锁的 CPU 所属的高速缓存中了

    锁操作比 CAS 操作更加耗时,因为锁操作的数据结构中需要两个原子操作

  • CAS算法应用:java原子类(automicXXX)

    Java1.7中 AtomicInteger.incrementAndGet()的实现源码为:

在这里插入图片描述

AtomicInteger.incrementAndGet的实现用了乐观锁技术,调用了类sun.misc.Unsafe库里面的 CAS算法,用CPU指令来实现无锁自增

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值