Java并发编程:volatile关键字

前言

valatile 关键字用到的不多,不过在源码中时常能够看到,对其既熟悉又陌生,熟悉的是这个名字,陌生的它的用意和用法,那么我们就对其一探究竟。

内存模型相关概念

计算机在执行程序时,每条指令都是在CPU中执行的,过程中势必会涉及到数据的读取和写入,而数据却是存放在主存(物理内存)当中,CPU的速度特别快,但是内存的读取操作相对于CPU的运算速度来说很慢,如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。因此在CPU里就有了Cache高速缓存。

  • CPU先把需要的数据从内存中读取复制一份到Cache
  • CPU进行计算的时候直接从Cache读取数据和写入数据
  • 结束后将Cache缓存中的数据刷新到主存

多线程的问题

内存模型

现如今我们的手机都是多核的,也就是说同时有几颗CPU在工作,每颗CPU都有自己的Cache高速缓存,这样也就产生了一个问题:

  • CPU1 和 CPU2 先将count变量读到各自的Cache中,假设都为1
  • CPU1 和 CPU2 分别开启一个线程对count进行自增操作,每个线程都有一块独立内存空间存放count的副本,自增后的count副本都为2
  • 自增完毕后,将结果副本写回内存,count等于2

进行了两次自增之后,写回内存的结果等于2,而自己想要的结果应该是count=3,这就是多线程并发带来的问题。

并发编程的三要素

1. 原子性

原子性,即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

一个经典的例子就是银行账户转账问题:

如果从账户A向账户B转账100元,那么必然要有两个操作:

  1. 从账户A减去100元
  2. 往账户B加上100元

设想一个,这两个操作不具备原子性,如果从账户A减去100元之后,操作突然中止,这就会造成,账户A虽然减去了100元,但是账户B并没有收到转过来的100元。所以这两个操作必须要具备原子性。

2. 可见性

可见性,指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值

比如执行线程A的是CPU1,执行线程B的是CPU2,当线程A去修改一个值 value = 5,会先把value的初始值加载到CPU1的Cache中,然后赋值为10,那么在CPU1的高速缓存当中value的值变为了10,却没有立即写入到主存中。
而当线程B去主存获取 value 的值,并加载到线程B的高速缓存中,此时内存当中的value还是5,那么就会使得获取到的值是5,而不是修改的值10。
线程A对变量value修改之后,线程B没有立即看到线程A修改的值,这也就是可见性的问题。对于可见性,Java提供了 volatile关键字来保证可见性,被volatile修饰的共享变量,它将保证修改的值会立即更新到主存,当有其他线程需要读取的时候,也会去内存读取最新值。

3. 有序性

有序性:即程序执行的顺序按照代码的先后顺序执行。

JVM在真正执行代码的时候不一定会保证按照代码的书写顺序进行,可能会发生指令重排序。

指令重排序

一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。处理器在进行重排序时会考虑指令之间的数据依赖性,如果两个语句之间没有数据依赖性,可能就会被重排序。
指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性。

volatile关键字

  1. volatile 保证可见性
  2. volatile 不能确保原子性
  3. volatile 保证有序性

普通的lock:

public class AtomTest {
    
    int res = 0;
    boolean lock = true;
    
    private void setLock() {
        res = 130;
        lock = false;
    }
    
    private void readInfo() {
        while (lock) {
            
        };
        System.out.println("res = " + res);
    }
    
    
    public static void main(String[] args) throws InterruptedException {
        final AtomTest atom = new AtomTest();
        
        Thread readThread = new Thread(new Runnable() {
            
            @Override
            public void run() {
                atom.readInfo();
            }
        });
        
        Thread lockThread = new Thread(new Runnable() {
            
            @Override
            public void run() {
                atom.setLock();
            }
        });
           
        readThread.start();
        TimeUnit.SECONDS.sleep(1);
        lockThread.start();
        readThread.join();
        lockThread.join();
        // join 方法使等待线程结果,才结束程序
        System.out.println("end");
    }
}

没有被volatile修饰的lock,修改的仅仅是副本的值,而没有立马写入到内存中去,使readThread一直在死循环中,程序也就无法结束。
volatile修饰的lock:

public class AtomTest {
    
    int res = 0;
    volatile boolean lock = true;
    
    private void setLock() {
        res = 130;
        lock = false;
    }
    
    private void readInfo() {
        while (lock) {
            
        };
        System.out.println("res = " + res);
    }
    
    
    public static void main(String[] args) throws InterruptedException {
        final AtomTest atom = new AtomTest();
        
        Thread readThread = new Thread(new Runnable() {
            
            @Override
            public void run() {
                atom.readInfo();
            }
        });
        
        Thread lockThread = new Thread(new Runnable() {
            
            @Override
            public void run() {
                atom.setLock();
            }
        });
           
        readThread.start();
        TimeUnit.SECONDS.sleep(1);
        lockThread.start();
        readThread.join();
        lockThread.join();
        // join 方法使等待线程结果,才结束程序
        System.out.println("end");
    }
}

打印结果:
res = 130
end
给lock加了volatile关键字修饰之后,当lockThread对lock做了修改之后,会立即更新修改到内存中,readThread发现副本已经过期,重新从内存获取lock的新值,跳出循环,执行完成。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容