JUC并发编程-CAS

目录

1. CAS简述

CAS操作的基本原理

2.  UnSafe类的CAS操作

3. CAS示例代码

4. CAS手写自旋锁

5. CAS中的ABA问题编码

6. CAS两大缺点


1. CAS简述

在Java中,CAS(Compare And Swap)中文翻译成比较并交换是一种重要的并发编程技术,它是Java并发包(java.util.concurrent,简称JUC)中许多同步类实现的基础。CAS操作包含三个操作数——内存位置(V),预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,就将内存位置的值修改为新值。这个操作是原子的,即线程在执行这个操作时不会被其他线程中断。

CAS操作的基本原理

CAS操作涉及到以下三个操作数:

  • 内存位置(V):一个内存地址,它存储着我们要修改的数据。
  • 预期值(A):我们假设这个内存位置的当前值是A。
  • 新值(B):如果内存位置的值确实是A,我们希望更新为B。

CAS操作执行时,会做以下几步:

  • 读取:从指定的内存位置读取值。
  • 比较:比较读取到的值和预期值A是否相等。
  • 交换(如果需要):如果步骤2中的比较结果为真(即读取的值等于预期值A),则将新值B写入该内存位置。如果比较结果为假(即读取的值不等于预期值A),则不进行任何操作。

2.  UnSafe类的CAS操作

  • compareAndSwapObject(Object o, long offset, Object expected, Object x): 原子地比较并交换对象字段。
  • compareAndSwapInt(Object o, long offset, int expected, int x): 原子地比较并交换整数字段。
  • compareAndSwapLong(Object o, long offset, long expected, long x): 原子地比较并交换长整数字段。

3. CAS示例代码

展示了如何使用AtomicInteger类来实现一个简单的CAS(Compare And Swap)操作。AtomicInteger类是Java并发包java.util.concurrent.atomic中的一部分,它提供了原子性操作的方法,这些方法底层使用了CAS指令。

import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {

    private AtomicInteger atomicInt = new AtomicInteger(0);

    public void safeUpdate(int newValue) {
        // 无限循环直到更新成功
        while (true) {
            // 获取当前值
            int current = atomicInt.get();
            // 尝试更新值,如果当前值未被其他线程改变,则更新成功
            if (atomicInt.compareAndSet(current, newValue)) {
                System.out.println("Value updated from " + current + " to " + newValue);
                break;
            }
            // 如果更新失败,则继续循环尝试
            System.out.println("Update failed, trying again...");
        }
    }

    public static void main(String[] args) {
        CASExample casExample = new CASExample();

        // 启动一个线程来尝试更新值
        new Thread(() -> {
            casExample.safeUpdate(5);
        }).start();

        // 启动另一个线程来尝试更新值
        new Thread(() -> {
            casExample.safeUpdate(10);
        }).start();
    }
}

4. CAS手写自旋锁

使用Java语言实现的简单自旋锁的示例。这个示例利用了AtomicReference类来模拟CAS操作,该类提供了原子性更新引用类型的方法。

import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<Thread>(null);

    public void lock() {
        Thread currentThread = Thread.currentThread();
        // 如果锁未被占用,则尝试获取锁
        while (!owner.compareAndSet(null, currentThread)) {
            // 自旋等待锁释放
        }
    }

    public void unlock() {
        Thread currentThread = Thread.currentThread();
        // 只有锁的持有者才能释放锁
        owner.compareAndSet(currentThread, null);
    }

    // 示例使用
    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();

        // 定义一个使用自旋锁的线程
        Runnable task = () -> {
            spinLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " acquired the lock.");
                // 模拟业务逻辑处理
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                spinLock.unlock();
                System.out.println(Thread.currentThread().getName() + " released the lock.");
            }
        };

        // 创建并启动多个线程
        for (int i = 0; i < 5; i++) {
            new Thread(task).start();
        }
    }
}

5. CAS中的ABA问题编码

ABA问题发生在多线程环境中,当以下情况发生时:

  • 线程A读取了一个值(假设为A)。
  • 线程A因为某些原因被挂起或阻塞,此时另一个线程B执行以下操作:
    • 将值从A改为B。
    • 完成一些工作后,又将值从B改回A。
  • 线程A恢复执行,它再次检查这个值,发现值仍然是A,因此认为这个值没有被其他线程改变过,并基于这个假设继续执行。、
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo {

    // 使用AtomicInteger演示ABA问题
    private static AtomicInteger atomicInt = new AtomicInteger(100);

    // 使用AtomicStampedReference来避免ABA问题
    private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(100, 0);

    public static void main(String[] args) throws InterruptedException {
        // 创建一个线程来演示ABA问题
        Thread threadA = new Thread(() -> {
            // 模拟ABA问题
            atomicInt.compareAndSet(100, 101);
            atomicInt.compareAndSet(101, 100);
        });

        // 创建一个线程来尝试在ABA问题发生后更新值
        Thread threadB = new Thread(() -> {
            boolean result = atomicInt.compareAndSet(100, 101);
            System.out.println("AtomicInteger: " + (result ? "Update successful" : "Update failed"));
        });

        // 启动线程A和B
        threadA.start();
        threadA.join();
        threadB.start();
        threadB.join();

        // 使用AtomicStampedReference避免ABA问题
        Thread threadC = new Thread(() -> {
            // 获取初始的标记
            int stamp = atomicStampedRef.getStamp();
            // 模拟ABA问题
            atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
            atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
        });

        // 创建一个线程来尝试在ABA问题发生后更新值
        Thread threadD = new Thread(() -> {
            int stamp = atomicStampedRef.getStamp();
            boolean result = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
            System.out.println("AtomicStampedReference: " + (result ? "Update successful" : "Update failed"));
        });

        // 启动线程C和D
        threadC.start();
        threadC.join();
        threadD.start();
        threadD.join();
    }
}

6. CAS两大缺点

CAS(Compare And Swap)操作虽然是一种高效的同步机制,但也存在一些缺点:

  • ABA问题

    • 问题描述:CAS操作需要检查一个变量的值是否仍然保持期望的值(A),如果是,则将其更新为新值(B)。然而,如果变量的值先是A,然后被另一个线程改变为B,然后又被改回A,这时CAS操作会认为变量没有被改变过,从而成功更新为新值B。这在某些情况下可能会导致错误的行为,因为变量的状态可能已经发生了重要的变化。
    • 解决方案:为了解决ABA问题,可以使用带有标记的版本号,例如AtomicStampedReference类,它不仅比较当前值,还比较版本号。
  • 自旋开销

    • 问题描述:在竞争激烈的环境中,CAS操作可能需要多次尝试才能成功。每次尝试都涉及一个自旋循环,这会消耗CPU资源。如果许多线程都在尝试对同一个变量进行CAS操作,那么这些线程将会持续消耗CPU资源,即使它们实际上并没有做任何有用的计算工作。
    • 解决方案:为了减少自旋的开销,可以采用以下策略:
      • 退避策略:在失败后,线程可以暂时休眠一段时间,然后再重试。
      • 线程挂起:在确定无法立即成功执行CAS操作时,线程可以选择挂起自己,而不是持续自旋。
      • 使用其他同步机制:在某些情况下,可能更适合使用传统的锁或其他同步机制,以避免CAS操作带来的开销。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

探索星辰大海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值