Android-CAS与原子变量(无锁并发和有锁并发)

Posted 天津 唐秙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android-CAS与原子变量(无锁并发和有锁并发)相关的知识,希望对你有一定的参考价值。

文章目录

无锁并发和有锁并发

技术点

1.CAS效率分析的原子变量
2.原子引用夏的ABA问题
3.原子更新器与累加器
4.LongAddr原理
5.unsafe实现原子数组

线程的上下文切换

  本质:CPU切换前把当前任务的状态保存下来,以便下次切换回这个任务时可以再次加载这个任务的状态,然后加载下一个任务的状态并执行,任务的状态保存及加载,这段过程就叫做上下文切换。
  每个线程都有一个程序计数器,记录要执行的下一条指令,一组寄存器保存当前线程的工作变量,堆栈记录执行历史,其中每一帧保存了一个已经调用但未返回的过程。
  寄存器是CPU内部的数量较少但是速度很快的内存,与之对应的是CPU外部相对较慢的RAM主内存,寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。
  程序计数器是一个专用的寄存器,用于表明指令序列中CPU正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置。
  上下文切换会导致额外的开销,常常表现为高并发执行时速度会慢串行,因此减少上下文切换次数便可以提高多线程程序的运行效率。
  直接消耗:指的是CPU寄存器需要保存和加载,系统调度器的代码需要执行,TLB实例需要重新加载,CPU的pipeline需要刷掉。
  间接消耗:指的是多核的cache之间得共享数据,间接消耗对于程序的影响要看线程工作区操作数据的大小。

CAS

  是一种策略,为了保证主内存中数据在被多个线程赋值的时候,是一个准确的,为了达到这个目的,采取的方案是:
  把旧值保留,拿旧值与主内存比对,如果不对,重读再加载。
  CAS必须和volatile关键字配合使用,原因,在保证线程安全性的前提下,要保证可见性
  volatile:是为了保证底层中线程间可见性
  synchronize:是为了保证线程安全性

问题:重读一样耗性能,那么synchronize切换也耗性能

  运用CAS的理论能完成锁功能一样的代码

CAS这种无锁模式去进行安全处理会比有锁快吗?
  1.你的线程数量不多,最好和CPU核数对应,他是最优的方案
  2.如果我一上来1000000线程CAS的效率绝对慢,因为既要发生上下文切换,还有数据读取的资源消耗问题。

  android的使用环境本身线程就不多,服务端才会比较多
  如果进程比较多,CAS也不适用
  CAS体现的是无锁并发,无阻塞并发,因为本身没有synchronized,线程不会陷入阻塞,这是效率提升的因素之一,如果竞争激烈,重试必然频繁发生,效率会下降,最好结果就是线程数不超过CPU核数

JUC并发包

内容
1.线程池
2.锁和条件变量
3.同步器
4.原子变量
5.阻塞队列
6.并发容器
7.并行计算框架
8.枚举

Atomic原子变量

本质上是一组工具,位置在atomic包下,处理并发安全问题上分为:
  1.单个原子处理
  2.块处理

对于单个属性进行处理,提供了Atomic工具
  AtomicInteger 原子整数
  所有的原子操作实际上是在底层进行了一个CAS的循环比较,只有达成目的才能退出。

ABA理论
  在多线程对于原子变量操作的时候,会发生将数据变更回去的现象,CAS在判定时会造成概念上的认知错误,但是实际上对于业务结果是不变的。但是实际业务运用过程中可能会需要知道整个运行过程中值是否发生改变,通过AtomicStampedReference追溯版本号,通过AtomicMarkableReference得到是否更改过

字段更新器
  AtomicReferenceFieldUpdater
原子累加器
  实际业务操作过程中,对于Android而言,最容易出现数据问题的是标志的累加

LongAddr累加原则
  根据CPU核数创建与之对应累加单元,将线程分配给对应的累加单元,最后将累加单元中的数据使用sum求和,累加单元是一个对象,不能使用数组,因为数组如果使用,假设数组的大小小于64字节,那么在读取的时候会一次性把整个数组读出来,在不同的高速缓存中进行计算时,会触发总线嗅探机制,数据并没有分开去处理,因为在A中改了,B中因为总线嗅探机制数据会失效。
@sun.misc.Contentded作用:在注解对象的前后各增加128字节大小的padding,为了就是让CPU将对象读取到不同的缓存行。

LongAdder伪共享原理和缓存行
什么是伪共享?
  CPU高速缓存的存储体系下,一个基本的缓存单位叫做缓存行,一个缓存行的大小为64byte,数组是一块连续的空间,因为副本数据的原因,数组加载到缓存当中,数据超过64字节会占用多行

总结:

对于并发处理,从业务角度出发看成两块:
  1.原子变量操作
  2.业务代码块的并发
并发手段现在接触的是两种:
  1.加锁并发:synchronize(悲观)
  2.无锁并发:CAS应用实现(乐观)依靠核的支撑,最大限度保证不进行线程上下文的切换

以上是关于Android-CAS与原子变量(无锁并发和有锁并发)的主要内容,如果未能解决你的问题,请参考以下文章

C++11并发,有锁队列和无锁队列

实战并发编程 - 02解决并发问题常用套路

盲猜原子变量内存屏障内存模型锁之间的关系

Go并发编程之美-CAS操作

并发编程(学习笔记-共享模型之无锁)-part5

实战Java高并发程序设计 5让普通变量也享受原子操作