CAS的实现原理及常见问题

一、实现原理

CAS(Compare-And-Swap)是一种基于比较和交换原理的原子操作机制,用于实现无锁编程。它通过一系列方法(如 compareAndSwapObjectcompareAndSwapIntcompareAndSwapLong 等)实现对变量的原子更新。这些方法的核心逻辑是:先比较当前值是否与预期值一致,如果一致,则将变量更新为新值,并返回 true;否则,操作失败,返回 false。这些方法的参数通常包括:目标对象、字段的内存偏移量、预期值和新值。

在底层实现中,CAS方法通过调用 native 方法来利用硬件指令完成操作。在x86架构中,CAS操作通常基于 cmpxchg 指令。然而,cmpxchg 指令本身并不具备跨多核CPU的原子性。为了确保在多核CPU环境下操作的原子性,通常需要结合 lock 前缀指令。lock 指令是一种CPU级别的锁,能够确保在多核环境下,当前操作对内存的访问是原子性的,从而避免并发冲突。

以下是一个使用 compareAndSwapInt 方法的示例,展示了如何通过 Unsafe 类实现原子操作

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class CASExample {
    private static final Unsafe UNSAFE;
    private static final long valueOffset;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);
            UNSAFE = (Unsafe) theUnsafe.get(null);

            valueOffset = UNSAFE.objectFieldOffset(CASExample.class.getDeclaredField("value"));
        } catch (Exception e) {
            throw new RuntimeException("Failed to initialize Unsafe", e);
        }
    }

    private volatile int value;

    public boolean compareAndSwapInt(int expectedValue, int newValue) {
        return UNSAFE.compareAndSwapInt(this, valueOffset, expectedValue, newValue);
    }

    public static void main(String[] args) {
        CASExample example = new CASExample();
        example.value = 10;

        // 尝试将值从10更新为20
        boolean success = example.compareAndSwapInt(10, 20);
        System.out.println("Update success: " + success); // 输出:Update success: true

        // 再次尝试将值从10更新为30,此时值已经是20,预期值不匹配
        success = example.compareAndSwapInt(10, 30);
        System.out.println("Update success: " + success); // 输出:Update success: false
    }
}

CAS机制通过比较和交换操作实现了无锁编程中的原子性,避免了传统锁机制带来的线程阻塞问题,从而提高了并发性能。在底层,CAS操作通过硬件指令(如 cmpxchglock)实现,确保了在多核CPU环境下的正确性和原子性。

二、CAS的短板及解决办法

1、ABA问题及解决办法

在并发环境中,一个变量的值在CAS操作过程中发生了多次变化,最终又回到了初始值,导致CAS操作误认为变量未被修改,从而引发潜在的线程安全问题。具体来说,假设一个共享变量的值是A,线程1将其改为B,线程2又将B改回A,而线程3在执行CAS操作时,会误认为变量的值从未改变,从而可能基于错误的假设进行操作。

模拟示例代码如下:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ABADemo {
    private static final AtomicInteger atomicInteger = new AtomicInteger(100);

    public static void main(String[] args) throws InterruptedException {
        // 线程1:将A改为B
        Thread thread1 = new Thread(() -> {
            try {
                // 模拟线程1执行时间较长
                TimeUnit.SECONDS.sleep(1);
                atomicInteger.compareAndSet(100, 200);
                System.out.println("Thread1: 改变value值 100 为 200");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 线程2:将B改为A
        Thread thread2 = new Thread(() -> {
            try {
                // 模拟线程2执行时间较长
                TimeUnit.SECONDS.sleep(2);
                atomicInteger.compareAndSet(200, 100);
                System.out.println("Thread2: 改变value值 from 200 为 100");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 线程3:将A改为C
        Thread thread3 = new Thread(() -> {
            try {
                // 模拟线程3执行时间较长
                TimeUnit.SECONDS.sleep(3);
                atomicInteger.compareAndSet(100, 300);
                System.out.println("Thread3: 改变value值 from 100 为 300");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        thread1.start();
        thread2.start();
        thread3.start();

        thread1.join();
        thread2.join();
        thread3.join();

        System.out.println("最后的值: " + atomicInteger.get());
    }
}

虽然最终值是300,但线程3的CAS操作成功了,而它并不知道值曾经被线程1和线程2修改过。这就是ABA问题的典型表现:线程3基于错误的假设(认为值从未改变)进行了操作。

解决办法

引入一个版本号或者时间戳来记录变量的修改次数,Java提供了 AtomicStampedReference 类来解决ABA问题。AtomicStampedReference 使用一个整型的版本号来记录每次修改,从而避免了ABA问题。

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABASolutionDemo {
    private static final AtomicStampedReference sharedValue = new AtomicStampedReference<>(100, 0); // 初始值为A(100),版本号为0

    public static void main(String[] args) throws InterruptedException {
        // 线程1:将A改为B
        Thread thread1 = new Thread(() -> {
            int[] stampHolder = {sharedValue.getStamp()}; // 获取当前版本号
            sharedValue.compareAndSet(100, 200, stampHolder[0], stampHolder[0] + 1); // 将A(100)改为B(200)
            System.out.println("Thread1: 修改值为 100 到 200, stamp: " + sharedValue.getStamp());
        });

        // 线程2:将B改为A
        Thread thread2 = new Thread(() -> {
            int[] stampHolder = {sharedValue.getStamp()}; // 获取当前版本号
            sharedValue.compareAndSet(200, 100, stampHolder[0], stampHolder[0] + 1); // 将B(200)改回A(100)
            System.out.println("Thread2: 修改值为 200 到 100, stamp: " + sharedValue.getStamp());
        });

        // 线程3:将A改为C
        Thread thread3 = new Thread(() -> {
            int[] stampHolder = {sharedValue.getStamp()}; // 获取当前版本号
            boolean success = sharedValue.compareAndSet(100, 300, stampHolder[0], stampHolder[0] + 1); // 尝试将A(100)改为C(300)
            if (success) {
                System.out.println("Thread3: 修改值为 100 到 300, stamp: " + sharedValue.getStamp());
            } else {
                System.out.println("Thread3: 修改 100 到 300失败, stamp: " + sharedValue.getStamp());
            }
        });

        // 启动线程
        thread1.start();
        thread1.join(); // 确保线程1先执行
        thread2.start();
        thread2.join(); // 确保线程2在线程3之前执行
        thread3.start();
        thread3.join();

        System.out.println("Final value: " + sharedValue.getReference());
        System.out.println("Final stamp: " + sharedValue.getStamp());
    }
}

2、自旋次数过多及解决办法

CAS(Compare-And-Swap)操作是一种无锁的并发控制机制,通过不断尝试更新变量值来实现线程安全。然而,在高并发场景下,如果多个线程频繁竞争同一个变量,CAS操作可能会失败多次,导致线程进入“自旋”状态,即不断重试。这种自旋会占用大量CPU资源,降低系统性能

import java.util.concurrent.atomic.AtomicInteger;

public class CASSpinExample {
    private static final AtomicInteger sharedValue = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        // 启动多个线程,模拟高并发场景
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                int expectedValue;
                int newValue;
                int spinCount = 0; // 记录自旋次数
                do {
                    expectedValue = sharedValue.get();
                    newValue = expectedValue + 1;
                    spinCount++;
                    // 如果自旋次数过多,打印警告信息
                    if (spinCount > 10000) {
                        System.out.println("Thread " + Thread.currentThread().getId() + " is spinning too much!");
                    }
                } while (!sharedValue.compareAndSet(expectedValue, newValue));
                // 如果CAS失败,会一直重试,可能导致大量自旋
                System.out.println("Thread " + Thread.currentThread().getId() + " completed after " + spinCount + " spins.");
            }).start();
        }

        Thread.sleep(2000); // 等待所有线程执行完毕
        System.out.println("Final value: " + sharedValue.get());
    }
}

改进方法一:定义了一个常量MAX_SPIN_COUNT,表示允许的最大自旋次数 ,当超出这个最大值,线程将不再CAS操作,直接退出线程

import java.util.concurrent.atomic.AtomicInteger;

public class CASSpinExample {
    private static final AtomicInteger sharedValue = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        // 启动多个线程,模拟高并发场景
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                int expectedValue;
                int newValue;
                int spinCount = 0; // 记录自旋次数
                final int MAX_SPIN_COUNT = 10000; // 设置最大自旋次数

                do {
                    expectedValue = sharedValue.get();
                    newValue = expectedValue + 1;
                    spinCount++;

                    // 如果自旋次数超过限制,采取替代措施
                    if (spinCount > MAX_SPIN_COUNT) {
                        System.out.println("Thread " + Thread.currentThread().getId() + " is spinning too much! Giving up.");
                        // 选择退出线程或暂停一段时间
                        // Thread.sleep(100); // 暂停100毫秒
                        return; // 退出线程
                    }
                } while (!sharedValue.compareAndSet(expectedValue, newValue));

                // 如果CAS成功,打印完成信息
                System.out.println("Thread " + Thread.currentThread().getId() + " completed after " + spinCount + " spins.");
            }).start();
        }

        Thread.sleep(2000); // 等待所有线程执行完毕
        System.out.println("Final value: " + sharedValue.get());
    }
}

改进方法二:使用synchronized或传统锁:在CAS操作失败多次后,退而使用传统的同步锁(如synchronizedReentrantLock),以减少CPU资源浪费。 

import java.util.concurrent.atomic.AtomicInteger;

public class CASWithLockFallback {
    private static final AtomicInteger sharedValue = new AtomicInteger(0);
    private static final Object lock = new Object();
    private static final int MAX_SPIN_COUNT = 10000;

    public static void main(String[] args) throws InterruptedException {
        // 启动多个线程,模拟高并发场景
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                int expectedValue;
                int newValue;
                int spinCount = 0; // 记录自旋次数

                do {
                    expectedValue = sharedValue.get();
                    newValue = expectedValue + 1;
                    spinCount++;

                    // 如果自旋次数超过限制,使用锁来完成操作
                    if (spinCount > MAX_SPIN_COUNT) {
                        synchronized (lock) {
                            sharedValue.incrementAndGet();
                        }
                        System.out.println("Thread " + Thread.currentThread().getId() + " used lock after " + spinCount + " spins.");
                        return; // 退出线程
                    }
                } while (!sharedValue.compareAndSet(expectedValue, newValue));

                // 如果CAS成功,打印完成信息
                System.out.println("Thread " + Thread.currentThread().getId() + " completed after " + spinCount + " spins.");
            }).start();
        }

        Thread.sleep(2000); // 等待所有线程执行完毕
        System.out.println("Final value: " + sharedValue.get());
    }
}

改进方法三:使用LongAdder代替AtomicLongLongAdder通过分段锁机制,将多个线程的操作分散到不同的分段上,从而减少冲突,提高性能。

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;

public class LongAdderExample {
    private static final LongAdder sharedValue = new LongAdder();

    public static void main(String[] args) throws InterruptedException {
        // 启动多个线程,模拟高并发场景
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                // 使用LongAdder的increment方法进行线程安全的递增操作
                sharedValue.increment();
                System.out.println("Thread " + Thread.currentThread().getId() + " completed.");
            }).start();
        }

        Thread.sleep(2000); // 等待所有线程执行完毕
        System.out.println("Final value: " + sharedValue.sum());
    }
}

 

 

 

你可能感兴趣的:(CAS)