CAS——无锁执行者 - 知识总结

1.CAS的全称是 Compare And Swap 即比较交换,其算法核心思想如下
2. 执行函数:CAS(V,E,N)

	其包含3个参数
		V:表示要更新的变量
		E:表示预期值
		N:表示新值

如果V值等于E值,则将V的值设为N。若V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。

3.由于CAS操作属于乐观派,它总认为自己可以成功完成操作,当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败,但失败的线程并不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。

4.假设存在多个线程执行CAS操作并且CAS的步骤很多,有没有可能在判断V和E相同后,正要赋值时,切换了线程,更改了值。造成了数据不一致呢?
CPU指令对CAS的支持:CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

5.鲜为人知的指针: Unsafe类--
-存在于sun.misc包中
内部方法操作可以像C的指针一样直接操作内存,单从名称看来就可以知道该类是非安全的,毕竟Unsafe拥有着类似于C的指针操作,因此总是不应该首先使用Unsafe类,Java官方也不建议直接使用的Unsafe类,但我们还是很有必要了解该类,因为Java中CAS操作的执行依赖于Unsafe类的方法,注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
Unsafe类中存在直接操作内存的方法;

		//分配内存指定大小的内存
		public native long allocateMemory(long bytes);

		//根据给定的内存地址address设置重新分配指定大小的内存
		public native long reallocateMemory(long address, long bytes);

		//用于释放allocateMemory和reallocateMemory申请的内存
		public native void freeMemory(long address);

		//将指定对象的给定offset偏移量内存块中的所有字节设置为固定值
		public native void setMemory(Object o, long offset, long bytes, byte value);

		//设置给定内存地址的值
		public native void putAddress(long address, long x);

		//获取指定内存地址的值
		public native long getAddress(long address);

		//设置给定内存地址的long值
		public native void putLong(long address, long x);

		//获取指定内存地址的long值
		public native long getLong(long address);

		//设置或获取指定内存的byte值

		//其他基本数据类型(long,char,float,double,short等)的操作与putByte及getByte相同

		public native byte getByte(long address);

		public native void putByte(long address, byte x);

		//操作系统的内存页大小
		public native int pageSize();

		提供实例对象新途径:
		//传入一个对象的class并创建该实例对象,但不会调用构造方法

		public native Object allocateInstance(Class cls) throws InstantiationException;

		/**类和实例对象以及变量的操作,就不贴出来了
		传入Field f,获取字段f在实例对象中的偏移量
		获得给定对象偏移量上的int值,所谓的偏移量可以简单理解为指针指向该变量的内存地址,
		通过偏移量便可得到该对象的变量
		通过偏移量可以设置给定对象上偏移量的int值
		获得给定对象偏移量上的引用类型的值
		通过偏移量可以设置给定对象偏移量上的引用类型的值*/

虽然在Unsafe类中存在getUnsafe()方法,但该方法只提供给高级的Bootstrap类加载器使用,普通用户调用将抛出异常,所以我们在Demo中使用了反射技术获取了Unsafe实例对象并进行相关操作。

6.Unsafe里的CAS 操作相关
在Java中无锁操作CAS基于以下3个方法实现:

	//第一个参数o为给定对象,offset为对象内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值,
	//expected表示期望值,x表示要设置的值,下面3个方法都通过CAS原子指令执行操作。
	public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);   
	public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
	public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

(1)、挂起与恢复
将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。
Java对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,其底层实现最终还是使用Unsafe.park()方法和Unsafe.unpark()方法
(2)、并发包中的原子操作类(Atomic系列)
(3)、原子更新基本类型

	原子更新基本类型主要包括3个类:

	AtomicBoolean:原子更新布尔类型
	AtomicInteger:原子更新整型
	AtomicLong:原子更新长整型

7.CAS的ABA问题及其解决方案

假设这样一种场景,当第一个线程执行CAS(V,E,U)操作,在获取到当前变量V,准备修改为新值U前,另外两个线程已连续修改了两次变量V的值,使得该值又恢复为旧值,这样的话,我们就无法正确判断这个变量是否已被修改过。

AtomicStampedReference类
AtomicStampedReference原子类是一个带有时间戳的对象引用,在每次修改后,AtomicStampedReference不仅会设置新值而且还会记录更改的时间。当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值才能写入成功,这也就解决了反复读写时,无法预知值是否已被修改的窘境
AtomicMarkableReference类
AtomicMarkableReference维护的是一个boolean值的标识,也就是说至于true和false两种切换状态,经过博主测试,这种方式并不能完全防止ABA问题的发生,只能减少ABA问题发生的概率

猜你喜欢

转载自blog.csdn.net/qq_38973672/article/details/87948329