资讯专栏INFORMATION COLUMN

一起学并发编程 - Volatile关键字详解

vpants / 2667人阅读

摘要:比如用修饰的变量,就会确保变量在修改时,其它线程是可见的。。多核环境中,多个线程分别在不同的中运行,就意味着,多个线程都有可能将变量拷贝到当前运行的里。当线程读取变量时,它将能看见被线程写入的东西。

volatile是用来标记一个JAVA变量存储在主内存(main memory)中,多线程读写volatile变量会先从高速缓存中读取,但是写入的时候会立即通过内存总线刷到主存,同时内存总线中会对这个变量进行监听,当发现数据变动时,会主动将该变量的CPU Cache置为失效。确切的说:每次写操作volatile变量时,将直接将主内存(main memory)中最新的值读取到当前Cache操作

概述

可见性: 是指线程之间数据可见共享,一个线程修改的状态对另一个线程是可见的。比如:用volatile修饰的变量,就会确保变量在修改时,其它线程是可见的。。

在多线程中,对非volatile变量进行操作的时候,出于对性能的考虑,当对这些变量进行数据操作时,线程可能会从主内存里拷贝变量到CPU Cache中去。多核CPU环境中,多个线程分别在不同的CPU中运行,就意味着,多个线程都有可能将变量拷贝到当前运行的CPU Cache里。

如下图所示(多线程数据模型):

示例 - 非Volatile
public class NotSharedObject {

    private static int COUNTER = 0;
    private static final int MAX_LIMIT = 5;

    public static void main(String[] args) {
        new Thread(() -> {
            int localValue = COUNTER;
            while (localValue < MAX_LIMIT) {
                if (localValue != COUNTER) {
                    System.out.printf("[线程] - [%s] - [%d]
", Thread.currentThread().getName(), COUNTER);
                    localValue = COUNTER;
                }
            }
        }, "READER").start();

        new Thread(() -> {
            int localValue = COUNTER;
            while (COUNTER < MAX_LIMIT) {
                System.out.printf("[线程] - [%s] - [%d]
", Thread.currentThread().getName(), ++localValue);
                COUNTER = localValue;
            }
        }, "UPDATER").start();
    }
}
[线程] - [UPDATER] - [1]
[线程] - [UPDATER] - [2]
[线程] - [UPDATER] - [3]
[线程] - [UPDATER] - [4]
[线程] - [UPDATER] - [5]

结果表明,UPDATE线程虽修改数据,但是READER线程并未监听到数据的变动,当前线程操作的是当前CPU Cache里的数据,而不是从main memory获取的。

couner 的变量未使用volatile关键字修饰即JVM无法保证有效的将CPU Cache的内容写入主存中。意味着 counter 变量在CPU Cache中的值可能会与主存中的值不一样。

如下图所示(无Volatile):

示例 - Volatile
private static volatile int COUNTER = 0;


[线程] - [UPDATER] - [1]
[线程] - [UPDATER] - [2]
[线程] - [READER] - [1]
[线程] - [UPDATER] - [3]
[线程] - [UPDATER] - [4]
[线程] - [UPDATER] - [5]
[线程] - [READER] - [3]

结果表明,volatile修饰后的变量并不会达到Lock的效果,它只会保证线程可见性,但不保证原子性,在读取volatile变量和写入它的新值时,由于操作耗时较短,就会产生 竞争条件:多个线程可能会读取到volatile变量的相同值,然后产生新值并写入主内存,这样将会覆盖互相的值。(有兴趣的可以在创建一个UPDATE线程测试效果)

如下图所示(Volatile):

Happens-Before 原则

Java5之后volatile关键字不仅能用于保证变量从主存中进行读写操作,同时还遵循Happens-Before原则,下文将会描述存在于volatile中的一些细节,想深入的可以自行谷歌 happens-before relationship或者访问提供的几个链接

参考文献(1):https://en.wikipedia.org/wiki/Happened-before

参考文献(2):http://preshing.com/20130702/the-happens-before-relation/

参考文献(3):http://www.importnew.com/17149.html

如果T1线程写入了一个volatile变量然后T2线程读取该变量,那么T1线程写之前对其可见的所有变量,T2线程读取该volatile之后也会对其可见。

禁止JVM指令重排优化,一旦被volatile修饰的变量,赋值后多执行了一个load addl $0x0, (%esp)操作,相当于多了一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),单核CPU访问内存时,并不需要内存屏障;

看看下面这个示例:

T1线程:
Object obj;
volatile boolean init;
---------T1线程------------
obj = createObj()    1;
init = true;        2;
---------T2线程------------
while(!init){
    sleep();
}
useTheObj(obj);

volatile修饰过的变量 init在写操作之前,创建了非volatile变量的obj,因而T1线程在写入init后,会将obj也写入主内存中去。

由于T2线程启动的时候读取被volatile修饰过的init,因而变量 init 和变量 obj 都会被写入T2线程所使用的CPU缓存中去。当T2线程读取 obj 变量时,它将能看见被T1线程写入的东西。

总结适用场景

线程可见,状态量标记

volatile boolean start = true;

while(start){
//
}
void close(){
    start = false;
}

屏障前后一致性,禁止指令重排

- 说点什么

全文代码:https://gitee.com/battcn/battcn-concurent/tree/master/Chapter1-1/battcn-thread/src/main/java/com/battcn/chapter13

个人QQ:1837307557

battcn开源群(适合新手):391619659

微信公众号:battcn(欢迎调戏)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67826.html

相关文章

  • java并发编程习之Volatile

    摘要:但是的语义不足以确保递增操作的原子性,在多线程的情况下,线程不一定是安全的。检查某个状态标记,以判断是否退出循环某个方法这边和用普通的变量的区别是,在多线程的情况下,取到后,的值被改变了,判断会不正确。 多线程为什么是不安全的 这边简单的讲述一下,参考java并发编程学习之synchronize(一) 当线程A和线程B同时进入num = num + value; 线程A会把num的值...

    thekingisalwaysluc 评论0 收藏0
  • 一起并发编程 - 优雅关闭

    摘要:文本将介绍两种可以优雅的终止线程的方式第一种在多线程模式中有一种叫两步终止的模式可以优雅的终止线程,这种模式采用了两个步骤来终止线程,所以叫两步终止模式。 Java中原来在Thread中提供了stop()方法来终止线程,但这个方法是不安全的,所以一般不建议使用。文本将介绍两种可以优雅的终止线程的方式... 第一种 在JAVA《Java多线程模式》中有一种叫Two-Phase Term...

    曹金海 评论0 收藏0
  • Java多线程习(三)volatile键字

    摘要:三关键字能保证原子性吗并发编程艺术这本书上说保证但是在自增操作非原子操作上不保证,多线程编程核心艺术这本书说不保证。多线程访问关键字不会发生阻塞,而关键字可能会发生阻塞关键字能保证数据的可见性,但不能保证数据的原子性。 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchroniz...

    tain335 评论0 收藏0
  • 者福音!可能是最适合你的Java习路线和方法推荐。

    摘要:学习完多线程之后可以通过下面这些问题检测自己是否掌握,下面这些问题的答案以及常见多线程知识点的总结在这里。可选数据结构与算法如果你想进入大厂的话,我推荐你在学习完基础或者多线程之后,就开始每天抽出一点时间来学习算法和数据结构。 我自己总结的Java学习的系统知识点以及面试问题,已经开源,目前已经 35k+ Star。会一直完善下去,欢迎建议和指导,同时也欢迎Star: https://...

    yanest 评论0 收藏0
  • 一起并发编程 - synchronized详解

    摘要:每个对象只有一个锁与之相关联。实现同步则是以系统开销作为代价,甚至可能造成死锁,所以尽量避免滥用。这种机制确保了同一时刻该类实例,所有声明为的函数中只有一个方法处于可执行状态,从而有效避免了类成员变量访问冲突。 synchronized是JAVA语言的一个关键字,使用 synchronized 来修饰方法或代码块的时候,能够保证多个线程中最多只有一个线程执行该段代码 ... 概述 ...

    acrazing 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<