Java 锁分类
Java 中的锁有很多,可以按照不同的功能、种类进行分类,下面是我对 Java 中一些常用锁的分类,包括一些基本的概述
- 成本系统中有一个数据表,表中有两个字段分别是 和 ,金额的属性是能够实时变化,而 version 表示的是金额每次发生变化的版本,一般的策略是,当金额发生改变时,version 采用递增的策略每次都在上一个版本 的基础上 + 1。
- 在了解了基本情况和基本信息之后,我们来看一下这个过程:公司收到回款后,需要把这笔钱放在金库中,假如金库中存有100 元钱
- 下面开启事务一:当男柜员执行回款写入操作前,他会先查看(读)一下金库中还有多少钱,此时读到金库中有 100 元,可以执行写操作,并把数据库中的钱更新为 120 元,提交事务,金库中的钱由 100 -> 120,version的版本 由 0 -> 1。
- 开启事务二:女柜员收到给员工发工资的请求后,需要先执行读请求,查看金库中的钱还有多少,此时的版本 是多少,然后从金库中取出员工的工资进行发放,提交事务,成功后版本 + 1,此时版本由 1 -> 2。
上面两种情况是最乐观的情况,上面的两个事务都是顺序执行的,也就是事务一和事务二互不干扰,那么事务要并行执行会如何呢/strong>
乐观锁的缺点
任何事情都是有利也有弊,软件行业没有完美的解决方案只有最优的解决方案,所以乐观锁也有它的弱点和缺陷:
ABA 问题
ABA 问题说的是,如果一个变量第一次读取的值是 A,准备好需要对 A 进行写操作的时候,发现值还是 A,那么这种情况下,能认为 A 的值没有被改变过吗以是由 A -> B -> A 的这种情况,但是 AtomicInteger 却不会这么认为,它只相信它看到的,它看到的是什么就是什么。
JDK 1.5 以后的 类就提供了此种能力,其中的 就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
也可以采用CAS的一个变种DCAS来解决这个问题。
DCAS,是对于每一个V增加一个引用的表示修改次数的标记符。对于每个V,如果引用修改了一次,这个计数器就加1。然后再这个变量需要update的时候,就同时检查变量的值和计数器的值。
循环开销大
我们知道乐观锁在进行写操作的时候会判断是否能够写入成功,如果写入不成功将触发等待 -> 重试机制,这种情况是一个自旋锁,简单来说就是适用于短期内获取不到,进行等待重试的锁,它不适用于长期获取不到锁的情况,另外,自旋循环对于性能开销比较大。
CAS与synchronized的使用情景
简单的来说 CAS 适用于写比较少的情况下(多读场景,冲突一般较少),synchronized 适用于写比较多的情况下(多写场景,冲突一般较多)
- 对于资源竞争较少(线程冲突较轻)的情况,使用 Synchronized 同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗 cpu 资源;而 CAS 基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
- 对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源,效率低于 synchronized。
资源已被锁定,线程是否阻塞
自旋锁的提出背景
由于在多处理器环境中某些资源的有限性,有时需要,这时候就需要引入锁的概念,只有获取了锁的线程才能够对资源进行访问,由于多线程的核心是CPU的时间分片,所以同一时刻只能有一个线程获取到锁。那么就面临一个问题,那么没有获取到锁的线程应该怎么办/p>
通常有两种处理方式:一种是没有获取到锁的线程就一直循环等待判断该资源是否已经释放锁,这种锁叫做自旋锁,它不用将线程;还有一种处理方式就是把自己阻塞起来,等待重新调度请求,这种叫做。
什么是自旋锁
自旋锁的定义:当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!