Java内存模型(JMM)-volatile
1.引入
多线程使用的是共享变量的副本,对副本进行更改,并不会影响其他线程。
public class VolatileVisibilityTest {
private static boolean initFlag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("waitig data....");
while(!initFlag) {}
System.out.println("++++++++++++++++");
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
prepareData();
}
}).start();
}
public static void prepareData() {
System.out.println("prepareData beg");
initFlag = true;
System.out.println("prepareData end");
}
}
执行上面代码,发现不同线程间对initFlag的操作互不影响。
如果在变量添加volatile
,不同线程之间就会相互感知。
private static volatile boolean initFlag = false;
2. jmm数据原子操作
- read(读取):从主内存中读取数据
- load(载入):将主内存读取到的数据写入工作内存
- use(使用):从工作内存读取数据来计算
- assign(赋值):将计算好的值重新赋值到工作内存中
- store(存储):将工作内存数据写入主内存
- write(写入):将store过去的变量值赋值给主内存中的变量
- lock(锁定):将主内存变量加锁,标识为线程独占状态
- unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量
早期加锁:例如线程B读取到initFlag,会给其lock,然后B通过一系列操作后,再通过write写入操作后的initFlag,最后才使用unlock,其他线程才可以访问initFlag
MESi缓存一致性协议
多个cpu从主内存读取同一个数据到各自的高速缓存,当其中某个cpu修改了缓存里的数据,该数据会马上同步回主内存,其它cpu通过总线嗅探机制可以感知到数据的变化从而将自己缓存里的数据失效
如果没有缓存一致性协议,那么一个线程对initFlag修改后,会等到该线程结束,才会将修改store到主内存中。
嗅探机制:其实是一种监听方式
Volatile缓存可见性实现原理
底层实现主要是通过汇编lock前缀指令,它会锁定这块内存区域的缓存(缓存行锁定)并回写到主内存
**1)**会将当前处理器缓存行的数据立即写回到系统内存。
**2)**这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效(MESI协议),然后通过监听获取主内存数据。
volatile也会对共享数据加锁,只是加锁不是在read过程,而是在store时给数据加锁。
思考:这样的好处是什么呢?
加锁的密度减小
·并发编程三大特性:可见性,原子性,有序性
. volatile保证可见性与有序性,但是不保证原子性,保证原子性需要借助
synchronized这样的锁机制
public class VolatileAtomic{
public static volatile int num = 0;
public static void increase() {
num++;
}
public static void main(String[]args)throws InterruptedExcepting{
Thread[] threads = new Thread[10];
for(int i=0;i<threads.length;i++){
threads[i] = new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<1000;i++){
increase();
}
}
});
threads[i].start();
}
for(Thread t : threads){
t.join();
}
System.out.println(num);
}
}
打印结果必定小于等于10000,原因就是volatile不能保证原子性