今天聊一下Java并发编程中的CAS,AQS以及ABA问题。
Java并发中的CAS是指Compare and Swap(比较并交换)操作。它是一种无锁的同步机制,用于实现多线程环境下的原子操作。
CAS操作包括三个操作数:内存位置(通常是一个变量)、预期值和新值。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是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问题是指在并发环境下,当一个值从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 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问题,确保在并发环境下对值的修改是可靠和安全的。