volatile 关键字
volatile 是 Java 中的关键字,用于修饰变量。它的作用是确保对被修饰变量的读写操作具有可见性和顺序性。
可见性:当一个线程修改了 volatile 变量的值,其他线程可以立即看到最新的值。这是因为 volatile 变量在修改时会强制将最新的值刷新到主内存中,并在读取时从主内存中获取最新的值。
顺序性:在使用 volatile 变量进行读写操作时,编译器和处理器会禁止指令重排序,保证了操作的顺序性。
不保证原子性
CAS(Compare and Swap)
CAS 是一种无锁的原子操作,用于实现多线程环境下的并发控制。CAS 操作包含三个参数:内存位置(变量地址)、期望值和新值。它的执行过程如下:
首先,它会比较内存位置中的值与期望值是否相等。
如果相等,则将新值写入该内存位置。
如果不相等,则表示其他线程已经修改了该内存位置,CAS 操作失败,需要重新尝试。
CAS 操作是一种乐观并发控制方式,相对于传统的使用锁的悲观并发控制方式,它可以提供更高的并发性能。
CAS 存在的 ABA 问题:
ABA 问题指的是在使用 CAS 进行比较时,可能出现值从 A 变为 B,然后再次变回 A 的情况。这种情况下,CAS 操作在比较时可能会成功,尽管中间的操作已经改变了变量的值。这可能引发一些潜在的问题。
此时CAS认为期望值和新值相等,会误认为可以修改变量,但其实变量已经发生了修改,CAS的原子性操作已无法保证!
为了应对ABA问题,Java 提供了 AtomicStampedReference 和 AtomicMarkableReference 类
@Test
void CASTest(){
AtomicStampedReference<String> casObject = new AtomicStampedReference<>("A", 0);
// 线程1:尝试修改值
Thread thread1 = new Thread(() -> {
int stamp = casObject.getStamp(); // 获取当前标记值
String oldValue = casObject.getReference(); // 获取当前引用值
try {
Thread.sleep(1000); // 为了模拟thread2线程的修改
} catch (InterruptedException e) {
e.printStackTrace();
}
// 尝试使用 CAS 修改值
boolean success = casObject.compareAndSet(oldValue, "C", stamp, stamp + 1);
log.info("Thread 1 - CAS operation success: " + success);
});
// 线程2:修改值为初始值
Thread thread2 = new Thread(() -> {
int stamp = casObject.getStamp(); // 获取当前标记值
String oldValue = casObject.getReference(); // 获取当前引用值
// 将值从 A 修改为 B
casObject.compareAndSet(oldValue, "B", stamp, stamp + 1);
System.out.println("Thread 1: Value changed to B");
// 将值从 B 修改回 A
stamp = casObject.getStamp();
casObject.compareAndSet("B", "A", stamp, stamp + 1);
System.out.println("Thread 1: Value changed back to A");
log.info("Thread 2 - Value changed to initial value");
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
/*
最后输出结果为 Final value: A
这说明thread1线程在修改变量casObject时保证了原子性,否则就会输出Final value: C
这是因为AtomicStampedReference不仅会比较期望值,还会比较标记值stamp,当两者都相等时才会执行Swap行为修改变量
*/
log.info("Final value: " + casObject.getReference());
}
由于修改AtomicStampedReference对象时,你可以在stamp标记位做标识,如果标记位的值发生了变化,那么CAS操作就会被取消,即有效避免了ABA问题。
关于CAS的一些思考
标签:Thread,修改,CAS,关键字,casObject,线程,volatile,stamp From: https://www.cnblogs.com/ashet/p/17729821.htmlCAS操作允许多个线程同时访问共享资源,但只有一个线程能成功地执行更新操作。
CAS是一种乐观锁的原子操作,是因为它相对于synchronized等悲观锁实现,CAS操作不需要加锁和解锁过程,因此减少了线程间的竞争和阻塞,提高了并发性能。悲观锁在占据资源时,其他线程访问该资源,则需要等待锁释放资源+竞争锁。
悲观锁担心资源很容易被修改,为了避免多个线程看到的资源不一致,因此并发访问在锁持有资源的时候就成了串行访问。
乐观锁认为只有资源被修改时,才会发生并发安全问题,因此直接允许多线程访问资源,当更新资源时,工作内存中资源的期望值与主内存中资源的值不相等时(线程访问的资源是从主内存拷贝到线程工作区间的资源,因此才能比较),才会做出限制。