Java并发中的CAS,AQS和ABA问题

今天聊一下Java并发编程中的CAS,AQS以及ABA问题。

CAS

Java并发中的CAS是指Compare and Swap(比较并交换)操作。它是一种无锁的同步机制,用于实现多线程环境下的原子操作。

CAS操作包括三个操作数:内存位置(通常是一个变量)、预期值和新值。CAS操作的执行过程如下:

  1. 读取内存位置的当前值。
  2. 比较当前值与预期值是否相等。
  3. 如果相等,则将新值写入内存位置。
  4. 如果不相等,则说明其他线程已经修改了内存位置的值,CAS操作失败。

CAS操作是原子的,即在执行过程中不会被其他线程中断。它通过比较内存位置的当前值与预期值来判断是否有其他线程修改了该值,从而实现了线程安全的原子操作。

在Java中,CAS操作主要通过java.util.concurrent.atomic包中的原子类来实现。常用的原子类包括AtomicInteger、AtomicLong和AtomicReference等。

下面是一个使用CAS操作的示例代码:

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        int oldValue;
        int newValue;
        do {
            oldValue = count.get();
            newValue = oldValue + 1;
        } while (!count.compareAndSet(oldValue, newValue));
    }

    public int getCount() {
        return count.get();
    }
}

在上面的示例中,Counter类使用AtomicInteger来实现一个计数器。increment方法使用CAS操作来实现原子递增操作,getCount方法返回当前计数器的值。

使用CAS操作可以避免使用锁来实现同步,从而提高并发性能。但需要注意的是,CAS操作并不适用于所有情况,特别是在存在竞争激烈的情况下,CAS操作的失败次数可能会增加,影响性能。因此,在使用CAS操作时需要根据具体情况进行评估和选择。

AQS

AQS是AbstractQueuedSynchronizer的缩写,它是Java提供的一个用于构建阻塞同步原语的框架。AQS使用FIFO队列来管理等待同步资源的线程。它维护一个表示同步资源当前状态的变量,该状态可以被多个线程共享,通常用于表示可用许可的数量或锁的状态。

要使用AQS,需要扩展AbstractQueuedSynchronizer类并实现tryAcquire和tryRelease方法。这些方法定义了获取和释放同步资源的逻辑。

以下是使用AQS实现简单锁的示例代码:

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class SimpleLock {
    private static class Sync extends AbstractQueuedSynchronizer {
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }

    private final Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);
    }

    public void unlock() {
        sync.release(1);
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }
}

在上面的示例中,Sync类扩展了AbstractQueuedSynchronizer,并重写了tryAcquire、tryRelease和isHeldExclusively方法。tryAcquire方法尝试通过将状态设置为1来获取锁,tryRelease方法通过将状态设置为0来释放锁,isHeldExclusively方法检查当前线程是否独占持有锁。

SimpleLock类使用Sync的实例提供锁功能。lock方法调用sync.acquire(1)来获取锁,unlock方法调用sync.release(1)来释放锁,isLocked方法调用sync.isHeldExclusively()来检查锁是否被持有。

AQS是一个强大的框架,可以高效地实现各种同步原语。它为Java中的许多并发工具(如锁、信号量和屏障)提供了基础。

ABA问题

ABA问题是指在并发环境下,当一个值从A变为B,然后再变回A时,可能会导致一些问题。这是因为在这个过程中,其他线程可能会修改这个值,而不知道它已经发生了变化。

为了解决ABA问题,可以使用版本号或时间戳来跟踪值的变化。每次修改值时,都会增加版本号或更新时间戳。这样,即使值从A变为B再变回A,版本号或时间戳也会发生变化,其他线程就能够察觉到这个变化。

在Java中,可以使用AtomicStampedReference类来解决ABA问题。AtomicStampedReference类可以存储一个带有版本号的引用,并提供了compareAndSet方法来比较引用和版本号是否匹配,并进行原子更新。

下面是一个使用AtomicStampedReference解决ABA问题的示例代码:

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAExample {
    private static AtomicStampedReference<String> value = new AtomicStampedReference<>("A", 0);

    public static void main(String[] args) {
        // Thread 1
        new Thread(() -> {
            int stamp = value.getStamp();
            String oldValue = value.getReference();
            String newValue = "B";
            if (value.compareAndSet(oldValue, newValue, stamp, stamp + 1)) {
                System.out.println("Thread 1: Value updated from A to B");
            }
        }).start();

        // Thread 2
        new Thread(() -> {
            int stamp = value.getStamp();
            String oldValue = value.getReference();
            String newValue = "A";
            if (value.compareAndSet(oldValue, newValue, stamp, stamp + 1)) {
                System.out.println("Thread 2: Value updated from B to A");
            }
        }).start();
    }
}

在上面的示例中,AtomicStampedReference类用于存储一个带有版本号的字符串引用。在两个线程中,分别尝试将值从A更新为B和从B更新为A。通过使用compareAndSet方法,可以确保在更新值时检查版本号是否匹配,从而解决ABA问题。

使用AtomicStampedReference可以有效地解决ABA问题,确保在并发环境下对值的修改是可靠和安全的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AQS(AbstractQueuedSynchronizer)是Java并发编程的一个重要类,它可以理解为抽象的队列同步器。AQS提供了一种基于FIFO队列的同步机制,用于实现各种同步器,如ReentrantLock、CountDownLatch、Semaphore等。 AQS的核心思想是使用一个volatile的int类型变量state来表示同步状态,通过CAS(Compare and Swap)操作来实现对state的原子更新。AQS内部维护了一个双向链表,用于保存等待获取同步状态的线程。 AQS的具体实现包括以下几个方面: 1. 内部属性:AQS内部有两个重要的属性,一个是head,表示队列的头节点;另一个是tail,表示队列的尾节点。 2. 入队操作:AQS的入队操作是通过enq方法实现的。在入队操作,首先判断队列是否为空,如果为空,则需要初始化队列;否则,将新节点添加到队列的尾部,并更新tail指针。 3. CAS操作:AQSCAS操作是通过compareAndSetHead和compareAndSetTail方法实现的。这些方法使用CAS操作来更新head和tail指针,保证操作的原子性。 4. 出队操作:AQS的出队操作是通过deq方法实现的。在出队操作,首先判断队列是否为空,如果为空,则返回null;否则,将头节点出队,并更新head指针。 5. 同步状态的获取和释放:AQS提供了acquire和release方法来获取和释放同步状态。acquire方法用于获取同步状态,如果获取失败,则会将当前线程加入到等待队列;release方法用于释放同步状态,并唤醒等待队列的线程。 通过继承AQS类,可以实现自定义的同步器。具体的实现方式是重写AQS的几个关键方法,如tryAcquire、tryRelease等,来实现对同步状态的获取和释放。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值