1、示例
public class AtomicDemo2 {
private static volatile int count = 0;
public static void inc(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++ ;
}
public static void main( String[] args ) throws InterruptedException {
for(int i=0;i<1000;i++){
new Thread(AtomicDemo2::inc).start();
}
Thread.sleep(4000);
System.out.println("运行结果:"+count);
}
}
运行结果:
从JMM内存分析:
问题1: 为什么单个变量不用volatile修饰就会有问题?
这里说的单个变量不用volatile修饰的有问题的特指long和double类型修饰的变量(64bit)。在一些32位的处理器上,如果要求对64位数据的写操作具有原子性,会有比较大的开销,为了照顾这种处理器,Java语言规范鼓励但不强求JVM对64位的long型变量和double型变量的写操作具有原子性。当JVM在这种处理器上运行时,可能会把一个64位long/double型变量的写操作拆分为两个32位的写操作来执行。这两个32位的写操作可能会被划分到不同的总线事务中执行,此时对这个64位变量的写操作将不具有原子
问题2:为什么i++这种的用volatile修饰不能保证其原子性呢?
javap : 字节码查看
其实i++这种操作主要可以分为3步:(汇编)
- 读取volatile变量值到local
- 增加变量的值
- 把local的值写回,让其它的线程可见
mov 0xc(%r10),%r8d ; Load
inc %r8d ; Increment
mov %r8d,0xc(%r10) ; Store
lock addl $0x0,(%rsp) ; StoreLoad Barrier
Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失。
史上最全的并发编程脑图:https://www.processon.com/view/5b1f1ad7e4b03f9d251c06e5#map