AtomicStampedReference、AtomicMarkableReference源码分析,解决cas ABA问题

cas的ABA问题就是 假设初始值为A,线程3和线程1都获取到了初始值A,然后线程1将A改为了B,线程2将B又改回了A,这时候线程3做修改时,是感知不到这个值从A改为了B又改回了A的过程:

AtomicStampedReference 本质是有一个int 值作为版本号,每次更改前先取到这个int值的版本号,等到修改的时候,比较当前版本号与当前线程持有的版本号是否一致,如果一致,则进行修改,并将版本号+1(当然加多少或减多少都是可以自己定义的),在zookeeper中保持数据的一致性也是用的这种方式;

AtomicMarkableReference则是将一个boolean值作是否有更改的标记,本质就是它的版本号只有两个,true和false,修改的时候在这两个版本号之间来回切换,这样做并不能解决ABA的问题,只是会降低ABA问题发生的几率而已;

看下面这个例子里面有主要源码的解析:

import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 解决atomic类cas操作aba问题,解决方式是在更新时设置版本号的方式来解决,每次更新就要设置一个不一样的版本号,修改的时候,不但要比较值有没有变,还要比较版本号对不对,这个思想在zookeeper中也有体现;
 * @author Administrator
 *
 */
public class AtomicStampedReferenceTest {

	public final static AtomicStampedReference<String> ATOMIC_REFERENCE = new AtomicStampedReference<String>("abc", 0);

	/**
	 *
	它里面只有一个成员变量,要做原子更新的对象会被封装为Pair对象,并赋值给pair;
	private volatile Pair<V> pair;
	
	先看它的一个内部类Pair ,要进行原子操作的对象会被封装为Pair对象
	private static class Pair<T> {
        final T reference;     //要进行原子操作的对象
        final int stamp;       //当前的版本号
        private Pair(T reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <T> Pair<T> of(T reference, int stamp) { //该静态方法会在AtomicStampedReference的构造方法中被调用,返回一个Pair对象;
            return new Pair<T>(reference, stamp);
        }
    }
	现在再看构造方法就明白了,就是将原子操作的对象封装为pair对象
	public AtomicStampedReference(V initialRef, int initialStamp) {
        pair = Pair.of(initialRef, initialStamp);
    }
           
           获取版本号    
           就是返回成员变量pair的stamp的值       
    public int getStamp() {
        return pair.stamp;
    }
	
            原子修改操作,四个参数分别是旧的对象,将要修改的新的对象,原始的版本号,新的版本号
            这个操作如果成功就会将expectedReference修改为newReference,将版本号expectedStamp修改为newStamp;
	public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        
        return
            expectedReference == current.reference && expectedStamp == current.stamp //如果原子操作的对象没有更改,并且版本号也没有更改, 
            &&
            (
            	(newReference == current.reference &&newStamp == current.stamp) //如果要修改的对象与就的对象相同,并且新的版本号也与旧的版本号相同,也就是重复操作,这时候什么也不干
            	||
             	casPair(current, Pair.of(newReference, newStamp)) //cas操作,生成一个新的Pair对象,替换掉旧的,修改成功返回true,修改失败返回false;
             );
    }
	 * @param args
	 */
	public static void main1(String[] args) {
		for (int i = 0; i < 100; i++) {
			final int num = i;
			final int stamp = ATOMIC_REFERENCE.getStamp();
			new Thread() {
				public void run() {
					try {
						Thread.sleep(Math.abs((int) (Math.random() * 100)));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (ATOMIC_REFERENCE.compareAndSet("abc", "abc2", stamp, stamp + 1)) {
						System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");
					}
				}
			}.start();
		}
		new Thread() {
			public void run() {
				int stamp = ATOMIC_REFERENCE.getStamp();
				while (!ATOMIC_REFERENCE.compareAndSet("abc2", "abc", stamp, stamp + 1))
					;
				System.out.println("已经改回为原始值!");
			}
		}.start();
	}
	
	
	/**AtomicMarkableReference 解决aba问题,注意,它并不能解决aba的问题 ,它是通过一个boolean来标记是否更改,本质就是只有true和false两种版本来回切换,只能降低aba问题发生的几率,并不能阻止aba问题的发生,看下面的例子**/
	public final static AtomicMarkableReference<String> ATOMIC_MARKABLE_REFERENCE = new AtomicMarkableReference<String>("abc" , false);
	
	public static void main(String[] args){
		//假设以下操作由不同线程执行
		System.out.println("mark:"+ATOMIC_MARKABLE_REFERENCE.isMarked()); //线程1 获取到mark状态为false,原始值为“abc”
		boolean thread1 = ATOMIC_MARKABLE_REFERENCE.isMarked();
		System.out.println("mark:"+ATOMIC_MARKABLE_REFERENCE.isMarked()); //线程3获取到mark状态为false ,原始值为“abc”
		boolean thread2 = ATOMIC_MARKABLE_REFERENCE.isMarked();
		System.out.println("change result:"+ATOMIC_MARKABLE_REFERENCE.compareAndSet("abc", "abc2", thread1, !thread1)); //线程1 更改abc为abc2
		
		System.out.println("mark:"+ATOMIC_MARKABLE_REFERENCE.isMarked()); //线程2获取到mark状态为true ,原始值为“abc2”
		boolean thread3 = ATOMIC_MARKABLE_REFERENCE.isMarked();
		System.out.println("change result:"+ATOMIC_MARKABLE_REFERENCE.compareAndSet("abc2", "abc", thread3, !thread3));//线程2 更改abc2为abc
		
		System.out.println("change result:"+ATOMIC_MARKABLE_REFERENCE.compareAndSet("abc", "abc2", thread2, !thread2));//线程3更改abc为abc2;
		//按照上面的执行顺序,3此修改都修改成功了,线程1与线程2拿到的mark状态都是false,他俩要做的操作都是将“abc”更改为“abc2”, 只是线程2在线程1和线程3中间做了一次更改,最后线程2做操作的时候并没有感知到线程1与线程3的更改;
	}
	
}


在多线程编程中,CAS(Compare and Swap)机制被广泛使用。它可以实现无锁并发,提高程序的性能。但是,CAS 机制存在 ABA 问题,即当一个值从 A 变为 B,再从 B 变回 A,这时另一个线程也会执行相同的操作,而我们无法区分这两次操作是否真正修改了值。为了解决这个问题,Java 提供了一个原子类 AtomicStampedReferenceAtomicStampedReference 可以保证在进行 CAS 操作时,不仅比较对象值是否相等,还会比较对象的时间戳是否相等。时间戳是一个整数值,每次对象值的改变都会导致时间戳的变化。因此,即使对象值从 A 变为 B,再从 B 变回 A,时间戳也会发生变化,从而避免了 ABA 问题的出现。 下面是一个使用 AtomicStampedReference 解决 ABA 问题的示例代码: ```java import java.util.concurrent.atomic.AtomicStampedReference; public class AtomicStampedReferenceDemo { static AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(1, 0); public static void main(String[] args) { new Thread(() -> { int stamp = reference.getStamp(); System.out.println(Thread.currentThread().getName() + " 第 1 次版本号:" + stamp); reference.compareAndSet(1, 2, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + " 第 2 次版本号:" + reference.getStamp()); reference.compareAndSet(2, 1, reference.getStamp(), reference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + " 第 3 次版本号:" + reference.getStamp()); }, "线程 1").start(); new Thread(() -> { int stamp = reference.getStamp(); System.out.println(Thread.currentThread().getName() + " 第 1 次版本号:" + stamp); // 等待线程 1 完成 CAS 操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } boolean isSuccess = reference.compareAndSet(1, 3, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + " 是否修改成功:" + isSuccess); System.out.println(Thread.currentThread().getName() + " 当前版本号:" + reference.getStamp()); System.out.println(Thread.currentThread().getName() + " 当前值:" + reference.getReference()); }, "线程 2").start(); } } ``` 输出结果: ``` 线程 1 第 1 次版本号:0 线程 1 第 2 次版本号:1 线程 1 第 3 次版本号:2 线程 2 第 1 次版本号:0 线程 2 是否修改成功:false 线程 2 当前版本号:2 线程 2 当前值:1 ``` 通过输出结果可以看出,线程 2 尝试将值从 1 改为 3,但是由于版本号已经被线程 1 修改过了,因此 CAS 操作失败,避免了 ABA 问题的出现。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值