博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java基础教程:多线程杂谈——Volatile
阅读量:5241 次
发布时间:2019-06-14

本文共 2367 字,大约阅读时间需要 7 分钟。

Java基础教程:多线程杂谈——Volatile

引入Volatile

  Java语言提供了一种稍弱的同步机制,即Volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为Volatile类型后,编译器与运行时都会注意到整个变量是共享的,因此不会将该变量上的操作和去其他内存操作一起重排序。Volatile变量不会被缓存在寄存器或者对其它处理器不可见的地方,因此在读取Volatile类型的变量时总会返回最新写入的值。

  

  Java内存模型告诉我们,各个线程会将共享变量从主内存中拷贝到工作内存,然后执行引擎会基于工作内存中的数据进行操作处理。线程在工作内存进行操作后何时会写到主内存中?这个时机对普通变量是没有规定的,而针对volatile修饰的变量给java虚拟机特殊的约定,线程对volatile变量的修改会立刻被其他线程所感知,即不会出现数据脏读的现象,从而保证数据的“可见性”
 

实现原理

  在生成汇编代码时会在volatile修饰的共享变量进行写操作的时候会多出Lock前缀的指令。我们想这个Lock指令肯定有神奇的地方,那么Lock前缀的指令在多核处理器下会发现什么事情了?主要有这两个方面的影响:

  1. 将当前处理器缓存行的数据写回系统内存;
  2. 这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效

  为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。因此,经过分析我们可以得出如下结论:

  1. Lock前缀的指令会引起处理器缓存写回内存;
  2. 一个处理器的缓存回写到内存会导致其他处理器的缓存失效;
  3. 当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值。

  这样针对volatile变量通过这样的机制就使得每个线程都能获得该变量的最新值。

 

无法保证原子性

public class VolatileExample {    private static volatile int counter = 0;    public static void main(String[] args) {        for (int i = 0; i < 10; i++) {            Thread thread = new Thread(new Runnable() {                @Override                public void run() {                    for (int i = 0; i < 10000; i++)                        counter++;                }            });            thread.start();        }        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(counter);    }}
  开启10个线程,每个线程都自加10000次,如果不出现线程安全的问题最终的结果应该就是:10*10000 = 100000;可是运行多次都是小于100000的结果,问题在于 volatile并不能保证原子性,在前面说过counter++这并不是一个原子操作,包含了三个步骤:
1.读取变量counter的值;
2.对counter加一;
3.将新值赋值给变量counter。
  如果线程A读取counter到工作内存后,其他线程对这个值已经做了自增操作后,那么线程A的这个值自然而然就是一个过期的值,因此,总结果必然会是小于100000的。原因在于volatile保证修改立即由当前线程工作内存同步到主内存,但其他线程仍需要从主内存取才能保证线程同步。
 

使用场景

  加锁机制既可以确保可见性又可以确保原子性,而volatile变量只可以确保可见性。

  当且仅当满足以下所有条件的时,才应该使用volatile变量:

  • 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值
  • 该变量不会与其他状态变量一起纳入不变性条件中
  • 在访问变量时不需要加锁

状态标记

volatile boolean inited = false;//线程1:context = loadContext();  inited = true;             //线程2:while(!inited ){sleep()}doSomethingwithconfig(context);

  

参考资料

 

转载于:https://www.cnblogs.com/MrSaver/p/11301190.html

你可能感兴趣的文章
python多线程下载网页图片并保存至特定目录
查看>>
《人工智能的未来》--------------经典语录
查看>>
了解循环队列的实现
查看>>
CentOS 简单命令
查看>>
Linux中修改docker镜像源及安装docker
查看>>
数位dp(模板+例题)
查看>>
javascript中强制类型转换
查看>>
python学习笔记
查看>>
php+ajax(jquery)的文件异步上传
查看>>
使用&nbsp;SharedPreferences 分类: Andro...
查看>>
TLA+(待续...)
查看>>
c#数据结构(第四章)
查看>>
PHP 小方法之 计算两个时间戳之间相差的日时分秒
查看>>
python selenium 基本常用操作
查看>>
用ping命令简单的测试 延时、抖动、丢包率
查看>>
题解: [GXOI/GZOI2019]与或和
查看>>
camon详细解决过程
查看>>
6.06—038—周四
查看>>
flash游戏服务器安全策略
查看>>
Eurekalog
查看>>