java关键字-volatile

2018-08-24  本文已影响10人  一个喜欢烧砖的人
前言
java相关的内存模型的概念
并发编程的三个概念
java 内存模型

从代码层面分析前面提的三个概念

x = 10;         //语句1
y = x;         //语句2
x++;           //语句3
x = x + 1;     //语句4

语句1是直接将数值10赋值给x,也就是说线程执行这个语句的会直接将数值10写入到工作内存中。
语句2实际上包含2个操作,它先要去读取x的值,再将x的值写入工作内存,虽然读取x的值以及 将x的值写入工作内存 这2个操作都是原子性操作,但是合起来就不是原子性操作了。
同样的,x++和 x = x+1包括3个操作:读取x的值,进行加1操作,写入新的值。
所以上面4个语句只有语句1的操作具备原子性。
总结:
java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和lock来实现,(因为锁可以保证同一时刻只有一个线程执行该代码块,从而保证了原子性)

剖析volatile的关键字

一旦一个变量被volatile修饰,就具备以下两种含义
1、 保证了不同线程对于这个变量进行操作时的可见性
2、禁止指令重排
注意:维度不能保障原子性,
首先先看个代码

//线程1
boolean stop = false;
while(!stop){
    doSomething();
}
 
//线程2
stop = true;

很多人都会这么干,但是这个会不会出错呢?答案是会出错的,因为线程中的数据和全局的数据是一样的吗?
解决的话 就是在stop 前面添加volatile关键字,他会在线程刷新数据的时候立马改变共享内存的数据
所以说:volatile无法保证原子性

public class Test {
    public volatile int inc = 0;
     
    public void increase() {
        inc++;
    }
     
    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
         
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

这串代码就能保证最后的结果是10000吗?答案也是no
因为虽然说线程在执行的时候会立马更新主存的数据,但是数据的++本身分为两个步骤,所以无法达到理想效果
1、可采用java atomic 包下面的 atomicinteger 的增加来计算
2、利用synchronized 和 lock 来实现原子性
总结:volatile实现可见性,是一个线程(cpu)的数据改变,他会更新主存的数据改变,同时他会让其他线程(cpu)的数据失效。

//x、y为非volatile变量
//flag为volatile变量
 
x = 2;        //语句1
y = 0;        //语句2
volatile flag = true;  //语句3
x = 4;         //语句4
y = -1;       //语句5

分析:1、2是一组 谁在前谁在后不一定
但是一定会在3前,同理,3肯定在4、5的前面,
4、5谁在前谁在后不一定

volatile的应用场景
volatile boolean flag = false;
 
while(!flag){
    doSomething();
}
 
public void setFlag() {
    flag = true;
}
volatile boolean inited = false;
//线程1:
context = loadContext();  
inited = true;            
 
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

一般都是作为多线程标记用的

上一篇 下一篇

猜你喜欢

热点阅读