共计 1930 个字符,预计需要花费 5 分钟才能阅读完成。
并发编程中,有三个非常重要的概念:
-
原子性:提供了一种互斥访问,同一时刻只能有一个线程对它进行操作
-
可见性:一个线程对主内存对修改可以及时的被其他线程观察到
-
有序性:一个线程观察其他线程的指令行执行顺序,由于指令重新排序的存在,该观察结果一般杂乱无序
而 volatile
关键字能够保证以下两点:
- 可见性(Visibility):
当一个线程修改了一个volatile
变量的值时,这个新值会立即被写入到主内存中,而不会先缓存在线程的本地内存中。同时,当其他线程读取这个volatile
变量时,它们会直接从主内存中读取最新的值,而不会使用本地缓存中的旧值。这样就确保了多个线程之间对共享变量的操作是可见的。
- 禁止指令重排序(Prevents Instruction Reordering):
volatile
关键字会禁止指令重排序优化,即在代码中volatile
变量之前的操作不会被重排序到volatile
变量之后,也不会被重排序到volatile
变量之后的操作之前。这样可以确保指令的顺序按照程序的顺序执行,避免了可能会影响多线程程序正确性的指令重排序。
需要注意的是,虽然volatile
关键字能够保证可见性和禁止指令重排序,但它并不能保证原子性,即不保证多线程操作的原子性。
另外,volatile
和 CAS(Compare and Swap,比较并交换)通常结合在一起使用,主要用于多线程编程中实现原子操作,特别是在并发环境下确保数据的一致性和可见性。
关于 CAS,java.util.concurrent.atomic
包提供了一系列原子操作的类,其中就包括了基于 CAS 的实现。最常见的就是java.util.concurrent.atomic.AtomicInteger
、java.util.concurrent.atomic.AtomicLong
等,它们提供了诸如compareAndSet()
等方法,用于在不使用锁的情况下进行原子性的操作。CAS 的使用可以提高并发性能,因为它避免了使用锁带来的线程阻塞和上下文切换。
使用 volatile 和 CAS 来实现一个线程安全的计数器:
import java.util.concurrent.atomic.AtomicInteger;
public class VolatileCASExample {
private volatile int counter = 0;
public int getCounter() {
return counter;
}
public void increment() {
int currentValue;
int newValue;
do {
currentValue = counter; // 获取当前值
newValue = currentValue + 1; // 计算新值
} while (!compareAndSwap(currentValue, newValue)); // CAS 操作,如果失败则重试
}
private synchronized boolean compareAndSwap(int expect, int update) {
if (counter == expect) { // 检查当前值是否与期望值相等
counter = update; // 如果相等,则更新为新值
return true;
}
return false;
}
public static void main(String[] args) {
final VolatileCASExample example = new VolatileCASExample();
final AtomicInteger total = new AtomicInteger(0);
// 创建多个线程并发增加计数器值
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
example.increment();
total.incrementAndGet();
}
};
// 启动多个线程
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(task);
threads[i].start();
}
// 等待所有线程完成
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 输出计数器的最终值
System.out.println("Final counter value: " + example.getCounter());
System.out.println("Total increments: " + total.get());
}
}
提醒:本文发布于211天前,文中所关联的信息可能已发生改变,请知悉!