这都可以(java并发的时候常用的处理方式)java并发处理数据,Java并发之volatile关键字,java中volatile关键字,
Java中的volatile关键字用来标记一个变量“被存储在主内存中”,意思是说每次读取volatile变量都会从出内存读取而不是CPU缓存,每次对volatile变量的写操作除了会更新相应的CPU缓存还会更新到主内存中。
实际上,从JDK1.5以后,volatile关键字还提供其他功能。
可见性问题
Java中的volatile关键字能够保证对变量的值的改变能够在多个线程都可见。
在多线程应用中,当多个线程操作一个非volatile变量时,基于性能考虑(CPU缓存速度高于主内存),每个线程会从主内存中将该变量的值拷贝到CPU的缓存中再进行操作。当程序部署在一个多CPU的计算机上时,每个线程其实是允许在不同的CPU上的,这会导致每个线程会先把该变量拷贝的它运行的CPU的缓存内,如图所示:
CPU缓存、线程、主内存关系图
当使用非volatile变量时,我们是无法保证在多线程情况下JVM对该变量的读写操作能够正常工作的,我们看以下代码:
我们开启了两个线程,希望两个线程能够交替运行打印,但实际上当程序运行一段时间后便不再打印了。原因是每个线程对应的CPU缓存都有一份flag变量的拷贝,并且是基于CPU缓存中的值进行计算的,JVM并不能保证每个线程对flag的改动都能立刻反应到其他线程中去。
volatile保证可见性
我们对上面的代码加上volatile再看看效果呢:
这时两个线程就能交替打印了,原因是当变量申明了volatile关键字,某个线程对变量的改动除了会更新该线程所运行的CPU缓存,还会更新主内存,同时让其他CPU缓存内有该变量的拷贝失效从而重新从主内存读取最新的值。
volatile对有序性的保证
基于性能原因,JVM和CPU是允许对代码进行重排的,JDK1.5之后进行了一些优化,我们来看下面的代码段:
在jdk1.5之前,针对testOrder方法,系统可能执行的顺序是这样的:
在jdk1.5后,做了一些有序性优化,a和b是必须在c之前执行的(a和b顺序可以调换),d和e是不需要在c之后执行(d和e顺序可以调换)。
那这个有什么用呢?我们来看下面这段代码:
如果init不加volatile,实际程序运行是init = true;可以先于initServiceA()执行,这就导致了SerivceA还没有初始化完成已经被Consumer调用了。
非原子操作
volatile不能保证非原子操作的一致性,请看如下代码:
这里我们期望value变量经过两个线程的1000次自增操作后能得到2000,但是实际情况是我们只能得到一个小于2000的值,为什么呢?因为value++在CPU执行的时候并不是一个原子操作,他大致会被分成三步:
1.从主内存读取value放入CPU缓存
2.对value加1
3.将value加1后的值赋给value,此时更新主内存
这里在多线程的情况下可能会产生上面代码错误的结果。
volatile和cas(Unsafe对象提供)是构成java并发包(JUC)的基石,后面会陆续介绍JUC的相关内容,欢迎关注和留言。
Demo代码位置
java-learning: java学习案例 - Gitee.com
参考:
http://tutorials.jenkov.com/java-concurrency/volatile.html#:~:text=The%20Java%20volatile%20keyword%20is%20used%20to%20mark,and%20not%20%20just%20to%20the%20CPU%20cache.
本文系作者 @河马 原创发布在河马博客站点。未经许可,禁止转载。
暂无评论数据