在学习并发之前,有必要了解清楚CAS是什么?
先参考:https://blog.csdn.net/v123411739/article/details/79561458
以下是记录一些代码:
import java.util.concurrent.atomic.AtomicInteger;
public class VolatileTest {
/**
* 分析此例子:
* 启动了20个线程,分别进行10000次对静态变量race进行+1操作
*
* 使用 volatile 修饰共享变量后,
* 每个线程要操作变量时会从主内存中将变量拷贝到本地内存作为副本,
* 当线程操作变量副本并写回主内存后,会通过 CPU 总线嗅探机制告知其他线程该变量副本已经失效,需要重新从主内存中读取。
*
* volatile 保证了不同线程对共享变量操作的可见性,
* 也就是说一个线程修改了 volatile 修饰的变量,当修改后的变量写回主内存时,其他线程能立即看到最新值。
*
*/
// public static volatile int race = 0;
//改成使用Java并发包原子操作类(Atomic) getAndIncrement方法可以保证原子操作
public static AtomicInteger race = new AtomicInteger(0);
private static final int THREADS_COUNT = 20;
public static void increase() {
race.getAndIncrement();
/**
* getAndIncrement方法使用了Unsafe中的getAndAddInt方法
* getAndAddInt(Object o, long offset, int delta)
* 根据o,offset拿到内存位置的最新值v,然后将该内存位置的值(即v)加上delta
* 修改,如果修改失败,则再尝试修改(使用CAS),直至修改成功
*
* CAS 在java中的应用是compareAndSwapInt --> 该方法使用了 Atomic::cmpxchg linux_x86 和 windows_x86 中都有不同实现
* windows和linux都使用了is_MP 和 LOCK_IF_MP
* Atomic::cmpxchg方法使用is_MP来判断是否为多处理器
* LOCK_IF_MP 会根据mp(1为多处理器)的值在汇编指令cmpxchg加lock
* intel 对 lock 前缀说明简要:
* 1.确保对内存的读-改-写操作原子执行(原子性)
* 2.禁止该指令与之前和之后的读和写指令重排序 (保证了具有valatile读和写的内存语义)
* 3.把写缓冲区中的所有数据刷新到内存中 (保证了具有valatile读和写的内存语义)
*
* 但此CAS也存在问题:
* 1.循环时间长开销大
* 2.只保证一个变量
* 3.ABA问题 (如果在这段期间它的值曾经被改成了B,后来又被改回为A,那CAS操作就会误认为它从来没有被改变过。)
*/
}
/**
* 不会得到等于20W原因:
* 当getstatic指令把race的值取到操作栈顶时,
* volatile关键字保证了race的值在此时是正确的(可见性),
* 但是在执行iconst_1、iadd这些指令的时候,
* 其他线程可能已经把race的值加大了,
* 而在操作栈顶的值就变成了过期的数据,
* 所以putstatic指令执行后就可能把较小的race值同步回主内存之中。
*
* volatile只能保证可见性,无法保证原子性
*/
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
increase();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) { //当活跃的线程数大于1
Thread.yield(); //使当前线程由执行状态,变成为就绪状态,
// 让出cpu时间,在下一个线程执行时候,此线程有可能被执行,也有可能没有被执行
}
System.out.println(race);
}
}