其实Java并发框架的基石一共有两块,一块是本文介绍的CAS,另一块就是AQS,后续也会写博客介绍。

什么是CAS机制

CAS机制是一种数据更新的方式。在具体讲什么是CAS机制之前,我们先来聊下在多线程环境下,对共享变量进行数据更新的两种模式:悲观锁模式和乐观锁模式。

悲观锁更新的方式认为:在更新数据的时候大概率会有其他线程去争夺共享资源,所以悲观锁的做法是:第一个获取资源的线程会将资源锁定起来,其他没争夺到资源的线程只能进入阻塞队列,等第一个获取资源的线程释放锁之后,这些线程才能有机会重新争夺资源。synchronized就是java中悲观锁的典型实现,synchronized使用起来非常简单方便,但是会使没争抢到资源的线程进入阻塞状态,线程在阻塞状态和Runnable状态之间切换效率较低(比较慢)。比如你的更新操作其实是非常快的,这种情况下你还用synchronized将其他线程都锁住了,线程从Blocked状态切换回Runnable华的时间可能比你的更新操作的时间还要长。

乐观锁更新方式认为:在更新数据的时候其他线程争抢这个共享变量的概率非常小,所以更新数据的时候不会对共享数据加锁。但是在正式更新数据之前会检查数据是否被其他线程改变过,如果未被其他线程改变过就将共享变量更新成最新值,如果发现共享变量已经被其他线程更新过了,就重试,直到成功为止。CAS机制就是乐观锁的典型实现。

CAS,是Compare and Swap的简称,在这个机制中有三个核心的参数:

  • 主内存中存放的共享变量的值:V(一般情况下这个V是内存的地址值,通过这个地址可以获得内存中的值)
  • 工作内存中共享变量的副本值,也叫预期值:A
  • 需要将共享变量更新到的最新值:B

如上图中,主存中保存V值,线程中要使用V值要先从主存中读取V值到线程的工作内存A中,然后计算后变成B值,最后再把B值写回到内存V值中。多个线程共用V值都是如此操作。CAS的核心是在将B值写入到V之前要比较A值和V值是否相同,如果不相同证明此时V值已经被其他线程改变,重新将V值赋给A,并重新计算得到B,如果相同,则将B值赋给V。

值得注意的是CAS机制中的这步步骤是原子性的(从指令层面提供的原子操作),所以CAS机制可以解决多线程并发编程对共享变量读写的原子性问题。

ABA问题

所谓ABA问题, 就是比较并交换的循环,存在一个时间差,而这个时间差可能带来意想不到的问题。
比如有两个线程A、B:
    一开始都从主内存中拷贝了原值为3;
    A线程执行到var5=this.getIntVolatile,即var5=3。此时A线程挂起;
    B修改原值为4,B线程执行完毕;
    然后B觉得修改错了,然后再重新把值修改为3;
    A线程被唤醒,执行this.CompareTxchange( )方法,发现这个时候主内存的值等于快照值3,(但是却不知道B曾经修改过),修改成功。
尽管线程A CAS操作成功,但不代表就没有问题。有的需求,比如CAS,只注重头和尾,只要首尾一致就接受。但是有的需求,还看重过程,中间不能发生任何修改。这就引出了原子引用。

原子引用
Int32对整数进行原子操作,如果是一个普通的对象呢?可以用 Interlocked.CompareExchange<T>(T, T, T)泛型来包装这个普通类,使其操作原子化。

C# 对CAS的ABA问题的解决方案

C#,通过Interlocked方法实现。CAS在.NET中的实现类是Interlocked,内部提供很多原子操作的方法,最终都是调用Interlocked.CompareExchange()
Windows,通过Windows API实现了InterlockedCompareExchangeXYZ系列函数。

CAS机制优缺点

CAS的适用场景

读多写少:如果有大量的写操作,CPU开销可能会过大,因为冲突失败后会不断重试(自旋),这个过程中会消耗CPU

单个变量原子操作:CAS机制所保证的只是一个变量的原子操作。

CAS总结

任何技术都不是完美的,当然,CAS也有他的缺点:
CAS实际上是一种自旋锁,
一直循环,开销比较大。
只能保证一个变量的原子操作,多个变量依然要加锁。
引出了ABA问题(C# Interlocked.CompareExchange()方法 可解决)。
而他的使用场景适合在一些并发量不高、线程竞争较少的情况,加锁太重。但是一旦线程冲突严重的情况下,循环时间太长,为给CPU带来很大的开销。

CAS机制的案例:

下面的基本示例展示了无锁堆栈中的 SpinWait 结构。 如果需要高性能的线程安全堆栈,请考虑使用 System.Collections.Concurrent.ConcurrentStack<T>

详解:启用3个线程给自定义堆栈LockFreeStack<T>的 字段reeStac 添加数据(0-20)。用到cas 技术保证了线程的同步。

LockFreeStack<int> reeStac = new();

for (int i = 1; i <=3; i++)
{
Thread se = new Thread(test);
se.Start();
} void test(){ for (int i = 0; i < 20; i++)
{
reeStac.Push(i); } } public class LockFreeStack<T>
{
private volatile Node m_head; private class Node { public Node Next; public T Value; } public void Push(T item)
{
var spin = new SpinWait();
Node node = new Node { Value = item }, head ;
while (true)
{
head = m_head;
node.Next = head;
Console.WriteLine("Processor:{0},Thread{1},priority:{2} count:{3} ", Thread.GetCurrentProcessorId(), Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Priority,item );
Node dd = Interlocked.CompareExchange(ref m_head, node, head);//如果相等 就把node赋值给m_head,返回值都是原来的m_head。
if (dd == head) break;//判断是否赋值成功。成功就跳出死循环。
spin.SpinOnce();
Console.WriteLine("Processor:{0},Thread{1},priority:{2} spin.SpinOnce()", Thread.GetCurrentProcessorId(), Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.Priority);
}
} public bool TryPop(out T result)
{
result = default(T);
var spin = new SpinWait(); Node head;
while (true)
{
head = m_head;
if (head == null) return false;
if (Interlocked.CompareExchange(ref m_head, head.Next, head) == head)
{
result = head.Value;
return true;
}
spin.SpinOnce();
}
}
}

最新文章

  1. Java继承与组合
  2. 7.3 数据注解特性之ConcurrencyCheck特性【Code-First系列】
  3. 使用 UICollectionView 实现日历签到功能
  4. 16.10.17学到的Java知识
  5. unittest测试驱动之HTMLTestRunner.py
  6. spring log4j.properties 没有日志的问题
  7. 《zw版&#183;Halcon-delphi系列原创教程》 Halcon分类函数006, image,影像处理(像素图)
  8. Socket编程基础——Socket选项
  9. sqlite查询结果在listview中展示
  10. csuoj 1334: 好老师
  11. ORA-12545: 因目标主机或对象不存在, 连接失败
  12. Nginx_handler模块发开(hello模块结构解析)
  13. Nginx服务器不支持PATH_INFO的问题及解决办法
  14. Java 字节数组类型(byte[])与int类型互转
  15. error: /lib64/libc.so.6: symbol _dl_starting_up, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference
  16. ng-if和ng-show的区别
  17. Java Build Path(Java 构建路径)
  18. 关于主机用户名显示为&quot;-bash-4.1$&quot;
  19. input模拟输入下拉框
  20. Linux Shell编程中的几个特殊符号命令 &amp; 、&amp;&amp; 、 ||

热门文章

  1. 一文读懂mysql权限系统
  2. bit操作常见trick
  3. gin中间request body绑定到不同的结构体中
  4. fluentd分布式日志管理系统
  5. python11day
  6. 【故障公告】数据库服务器 CPU 100% 引发全站故障
  7. Redis 学习笔记(一)redis 数据类型和对象机制
  8. Java 中对象锁和类锁的区别? 关键字 Synchronized的用法?
  9. 深入Windows APC
  10. 业务4P分析实践