并发编程中有关CAS操作的学习记录

什么是悲观锁、乐观锁?在java语言里,总有一些名词看语义跟本不明白是啥玩意儿,也就总有部分面试官拿着这样的词来忽悠面试者,以此来找优越感,其实理解清楚了,这些词也就唬不住人了。

  • synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。
  • CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

那么问题来了,什么是CAS操作?

CAS是Compare-and-swap(比较与替换)的简写,是一种有名的无锁算法

比较并操作,CPU指令,在大多数处理器架构,包括IA32、Space中采用的都是CAS指令,CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。


相关术语:

这里写图片描述

这里写图片描述

2.处理器是如何实现原子操作

32位IA-32处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作。首先处理器会自动保证基本的内存操作的原子性。处理器保证从系统内存中读取或写入一个字节是原子的,意思是当一个处理器读取一个字节时,其他处理器不能访问这个字节的内存地址。

2.1 总线锁定

如果多个处理器一起对共享变量进行读改写操作(i++就是典型的读改写操作),这个读改写操作就不是原子的。如上图所示,各个处理器将i的值读入自己的处理器缓存中,各自对各自的缓存里的i值进行操作,然后分别写入系统内存从而导致了问题的产生。要想对共享变量的读改写操作也是原子性的,必须保证,CPU1读改写共享变量的时候,CPU2不能操作该共享变量内存地址的缓存行。

处理器的总线锁就是这么来保证原子性的,所谓总线锁就是使用处理器提供的一个LOCK #信号,当一个处理器在总线上输出此信号时,其他处理器的请求将会被阻塞住,那么该处理器可以独占共享内存。

2.2 缓存锁定

在同一时刻我们只需要保证对某一个内存地址的操作是原子性就可以了,但是总线锁把CPU和内存之间的通信锁住了,这使得锁定期间,其他处理器不能操作其他内存地址的数据,所以总线锁定的开销比较大。

频繁使用的内存会缓存在处理器的L1,L2,L3高速缓存中,那么原子操作就可以直接在处理器内部缓存进行,并不需要声明总线锁。

缓存锁是:一个处理器的缓存写回到内存会导致其他处理器的缓存无效,在多核处理器中,例如在Pentium和P6 family处理中,如果通过嗅探一个处理器来检测到其他处理器打算写内存地址,而这个地址当前处于共享状态,那么正在嗅探的处理器将使它的缓存行无效,在下次访问相同内存地址时,强制执行缓存行填充。

但是有两种情况是不能使用缓存锁:一是不能缓存到处理器的数据以及跨多个缓存行的数据;而是有些处理器不支持缓存锁定。
 

 

 

看一看compareAndSet方法的实现,以及方法所依赖对象的来历:

compareAndSet方法的实现很简单,只有一行代码。这里涉及到两个重要的对象,一个是unsafe,一个是valueOffset

什么是unsafe呢?Java语言不像C,C++那样可以直接访问底层操作系统,但是JVM为我们提供了一个后门,这个后门就是unsafe。unsafe为我们提供了硬件级别的原子操作

至于valueOffset对象,是通过unsafe.objectFieldOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量。我们可以简单地把valueOffset理解为value变量的内存地址。

我们在上一期说过,CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B

而unsafe的compareAndSwapInt方法参数包括了这三个基本元素:valueOffset参数代表了V,expect参数代表了A,update参数代表了B。

正是unsafe的compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。

unsafe:对应的是Unsafe类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。JDK中有一个类Unsafe,它提供了硬件级别的原子操作。JDK API文档也没有提供任何关于这个类的方法的解释。从描述可以了解到Unsafe提供了硬件级别的操作,比如说获取某个属性在内存中的位置,比如说修改对象的字段值,即使它是私有的。

value:volatile修饰的变量,内存中其他线程具有可见性。加或减都是对这个变量值进行修改。

valueOffset:这里指的就是value这个属性在内存中的偏移量(内存中的地址,而不是值),当类被加载时先按顺序初始化static变量和static块,通过unsafe中public native long objectFieldOffset(Field paramField);

/** Returns the memory address offset of the given static field.

   * The offset is merely used as a means to access a particular field
   * in the other methods of this class.  The value is unique to the given
   * field and the same value should be returned on each subsequent call.
   * 返回指定静态field的内存地址偏移量,在这个类的其他方法中这个值只是被用作一个访问
   * 特定field的一个方式。这个值对于 给定的field是唯一的,并且后续对该方法的调用都应该
   * 返回相同的值。
   *
   * @param field the field whose offset should be returned.
   *              需要返回偏移量的field
   * @return the offset of the given field.
   *         指定field的偏移量
   */
  public native long objectFieldOffset(Field field);

获取AtomicInteger类属性value在内存中的偏移量,并将偏移量值赋给valueOffset。需要强调valueOffset代表的不是value值在内存中的位置,而是这个属性在内存中的地址。

 

参考资料:

https://www.jianshu.com/p/bab16d7c83a2

https://www.cnblogs.com/gdjdsjh/p/5076815.html

https://www.sohu.com/a/215510186_465221

https://www.cnblogs.com/hupu-jr/p/7927635.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值