关键字volatile,公共堆栈,私有堆栈,非原子特性

一、公共堆栈与线程的私有堆栈

在启动线程时,变量的值是存在于公共堆栈及线程的私有堆栈中。在JVM被设置为-server 模式时为了线程运行的效率,线程一直在私有堆栈中取变量的值,即使有其他线程将变量的值进行了修改,更新的却是公共堆栈中的变量值,私有堆栈中的值不会变,这就导致线程运行存在问题。内存关系如图:

····························

volatile作用

使用volatile 关键字,可以强制的从公共内存中读取值。使用volatile关键字增加了实例变量在多个线程之间的可见性。但是volatile关键字的缺点是不支持原子性。

volatile与synchronized 比较

1)volatile 是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,但是volatile只能修饰变量,而synchronized可以修饰方法,代码块。 2)多线程访问volatile 不会发生阻塞,而synchronized 会发生阻塞。 3)volatile 能保证数据的可见性,但不能保证原子性,而 synchronized可以保证原子性,也可以保证可见性,因为它会将私有内存和公共内存中的数据做同步。

volatile的非原子特性

程序的原子性概念:一个操作的过程是不可中断的,要么全部执行成功要么全部执行失败,有着“同生共死”的感觉。即使在多个线程一起执行的时候,一旦一个原子性的操作开始后,就不会受其他线程的干扰。

验证

public class MyThread extends Thread{

// static修饰的变量只被初始化一次,下一次依据上一次结果值

volatile public static int count;

private static void addCount(){

for (int i = 0; i < 100; i++){

count++;

}

System.out.println("count="+count);

}

@Override

public void run(){

addCount();

}

}

public class VolatileRun {

public static void main(String[] args) {

MyThread[] mythreadArr = new MyThread[100];

// 创建 100个实例

for(int i = 0; i < 100; i++){

mythreadArr[i] = new MyThread();

}

for(int i = 0; i < 100; i++){

mythreadArr[i].start();

}

}

}

运行结果:不是10000 ··························

关键字volatile 提示线程每次从公共堆栈读取值,而不是从私有内存中读取,这样就保证了数据的同步数据可见性。但是:如果修改实例变量中的数据,比如 i++,也就是i = i+1,则这样的操作其实并不是一个原子操作,也就是非线程安全的。表达式i++ 的操作步骤分解如下: 1)从内存中取出 i 的值; 2)计算 i 的值; 3)将 i 的值写到内存中

假如在第2步计算值的时候,另一个线程也修改了 i 的值,那么这时候就会出现脏数据。解决办法就是使用synchronized 关键字。所以说 volatile 本身并不处理数据的原子性,只是强制数据的读写及时影响到主内存中。

用图来演示下volatile出现非线程安全的原因。变量在内存中工作的过程。 1)read 和 load 阶段:读取和加载 2)use 和 assign 阶段: 操作和赋值 3)store 和 write 阶段:储存和 写 在多线程环境,use和assign 是多次出现的,但这一操作不是原子性的,在read和load之后,如果主内存的count变量的值修改之后,线程工作内存中的值 由于已经加载,不会产生对应变化,也就是私有内存和公共内存中的变量不同步了,所以计算出的结果和预期结果不一样,也就出现了非线程安全的问题。


关于古代中国妓院,八则有趣的冷知识
《模拟城市:我是市长》如何重新开始 重新开始方法一览