【Java 并发编程】线程锁机制 ( 悲观锁 | 乐观锁 | CAS 三大问题 | ABA 问题 | 循环时间长问题 | 多个共享变量原子性问题 )

2023-03-29 16:52:22 浏览数 (1)

文章目录

  • 一、悲观锁
  • 二、乐观锁
  • 三、乐观锁 CAS 三大问题

一、悲观锁


假设有

2

个线程 , 线程 A 和 线程 B ; 线程 A 访问共享资源 , 线程 B 等待 , 一旦线程 A 访问结束 , 线程 B 访问该共享资源 ;

悲观锁 : 只要有 线程 来操作 共享资源 , 就认为肯定 有其它若干线程也要操作该共享资源 , 一定要 对共享资源进行加锁 ; 任何情况下 , 哪怕 只有一个线程访问共享资源 , 也要对该共享资源进行加锁 ; ( 持有悲观的态度 )

对共享资源加锁 , 会对该资源产生负面影响 , 效率会降低 ;

如果只是 单线程访问资源 , 不会产生并发问题 , 没有必要进行加锁 ; 如果加了锁 , 执行效率变低 , 造成了资源浪费 ;

synchronized 就是悲观锁 ;

二、乐观锁


乐观锁 : 持有乐观的态度 , 线程 A 访问共享资源 , 乐观的认为只有

1

个线程访问该资源 , 不用进行加锁 ;

线程 A 访问主内存变量前 , 记录下值

X

, 线程 A 访问完毕后 , 会将最终的值同步到主内存中 , 此时会检查下 主内存中变量的值是否还是

X

,

  • 如果是 , 则说明 线程 A 访问期间 没有线程修改该变量值 , 那么将线程 A 计算的 新值更新到主内存中
  • 如果不是 , 主内存中的变量值变成了
Y

, 那么说明该值 被其它线程修改了 ; 那么将当前值抛弃 , 重新从主内存获取变量值

Y

, 然后线程 A 继续执行 , 执行完毕后将计算结果同步到主内存变量中 , 再次对比主内存中的变量值是否是

Y

, 如果是可以更新 , 如果不是 , 那么再次重复本操作 ;

乐观锁 , 全程没有加锁 , 没有阻塞 , 只要判定主内存中被访问的共享变量 , 线程计算之前的值与计算之后的值一致 , 就更新到主线程中 ;

三、乐观锁 CAS 三大问题


CAS : Compare and Swap , 比较再交换 , 是乐观锁中的线程访问完共享变量后 , 先进行变量比较 , 然后在同步共享变量值 ;

JDK

1.5

之后提供的 java.util.concurrent 包中的类 , 解决了 CAS 相关问题 ;

java.util.concurrent 包简称 J.U.C ;

CAS 解决的

3

大问题 :

ABA 问题 :

  • 问题描述 : 线程 A 访问变量 X = 0 , 访问期间 , 线程 B 访问 X 将其改为 1 , 然后 线程 C 访问 X 将其又改为 0 , 此时线程 A 访问完毕后 , 查询发现变量 X 仍然是 0 , 认为期间没有线程访问该变量 ;
  • 解决方案 : 给变量设置一个版本号 , 每次线程访问变量时 , 版本号 1 , 这样每次判断变量的版本号即可 ;

循环时间过长 :

  • 问题描述 : 乐观锁中 , 假如连续多次写回数据时 , 发现值改变 , 校验失败 , 导致 重复执行线程代码 , 会给 CPU 带来很大开销 , 这些 CPU 时间片都浪费了 ;
  • 解决方案 : 将锁升级 ;

保证多个共享变量原子性问题 :

  • 问题描述 : 针对 单个共享变量 访问时 , 使用 atomic 原子类 可以使用 CAS 保证原子操作 , 如果 有多个共享变量 , CAS 无法保证操作的原子性 ;
  • 解决方案 : 使用 JDK 提供的 AtomicReference 封装多个变量到一个类对象中 , 保证共享变量的原子性 ;

线程 A 访问共享变量的操作 , 不是原子操作 , 就会导致如下问题 :

线程 A 访问变量 X , 执行完毕后 , 变量 X 值原始值进行比较 , 比较相等 , 将数据更新到主内存 , 如果在 比较相等后 , 在 数据更新到主内存之前 , 有 另外一个线程 B 修改了该变量 X , 这样就出现了问题 ;

0 人点赞