Java并发编程之可见性、原子性和有序性解析

古语有云,天上的一天,地上的一年,当年玉帝妹子私自下凡间,与杨天佑结为夫妇,有一天玉帝突然想起,妹妹呢,咋好几天没见到她了,虽然在天上只是几天时间,而在凡间玉帝妹子和杨君都有了仨孩子啦,这也才有了后来二郎真君劈山救母的故事。

劈山救母

言归正传,其实在计算机的世界里同样存在这样的矛盾,那就是CPU、内存和I/O设备之间速度差异。根据木桶效应,即一个木桶能装多少水取决于它最短的那块木板,I/O设备的瓶颈制约着软件的性能。

为了应对这个问题,从计算机体系结构层面、操作系统层面和程序编译层面都有相应的优化措施:
1)计算机体系层面,CPU增加缓存,均衡了CPU与内存之间的速度差异;
2)操作系统层面,引入了进程和线程,以时分复用的方式均衡CPU与I/O设备之间的速度差异;
3)编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。

然而没有一劳永逸的方法,在享受这些便利的时候,我们也要承受它给我们带来的困扰,这些优化就是很多并发编程中诡异问题的根源所在,主要表现为三个方面:可见性问题、原子性问题和有序性问题。

1. 可见性问题

可见性,即一个线程对共享变量的修改,另一个线程能够立即看到,在多核时代,每个CPU都有自己的缓存,如下图所示,线程1操作CPU01的缓存,线程2操作CPU02的缓存,显然线程1对共享变量的操作对于线程2来说就不具备可见性。

可见性问题

我们可以通过如下程序验证这个问题

public class CurrencyTest {
    int count = 0;
    public void countAdd() {
        for(int i = 0; i < 10000; i++)
        count+=1;
    }
    public static void main(String[] args) throws InterruptedException {
        CurrencyTest currencyTest = new CurrencyTest();
        Thread thread1 = new Thread(() -> currencyTest.countAdd());
        Thread thread2 = new Thread(() -> currencyTest.countAdd());
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(currencyTest.count);
    }
}

运行上面的代码,其结果是10000到20000之间的一个随机数,这就是可见性问题引起的,每个CPU中都有共享变量count,自己玩自己的,每个线程都是根据各自CPU中的缓存值操作,最后就会出现数据不一致的问题。

2. 原子性问题

即使是在单核系统中,仍然能够边上网边听歌,这就得益于多线程时分复用的出现,当年Unix也是因为这个而名扬天下的。它解决了I/O等待时间长阻塞线程而浪费CPU资源的问题,多线程时分复用的原理如下图所示,将CPU划分为时间片,在一个时间片内,如果一个进程进行一个 IO 操作,例如读个文件,这个时候该进程可以把自己标记为“休眠状态”并出让 CPU的使用权,待文件读进内存,操作系统会把这个休眠的进程唤醒,唤醒后的进程就有机会重新获得 CPU 的使用权了。

原子性问题

Java中的并发编程是基于多线程的,会涉及到任务切换(任务切换通常指的就是线程切换),线程切换也是诡异Bug的源头之一,线程的切换可以发生在程序运行的任何一条指令,注意这里强调的是指令而不是Java中的一条代码,例如我们熟悉的i++操作就是🈶️三条指令完成的,
1)把变量i的值加载到CPU的寄存器中;
2)在寄存器中执行+1操作;
3)将结果写入内存,缓存机制可能导致结果写入CPU缓存而不是内存中。
由于存在线程的切换,i++的操作可能被中断,引起数据不一致的问题,我们把一个或者多个操作在CPU中执行的过程中不被中断的特性称为原子性。

3. 有序性问题

编译阶段的指令重排序会导致顺序性问题,从硬件架构上来看,指令重排序是指CPU采用了允许将多条指令不按程序规定的顺序分开发送各相应电路单元处理的方式,但并不是说指令任意排序,指令重排序不能影响正确的执行结果。周志明老师《深入理解Java虚拟机》一书中总结道:Java程序中天然的有序性可以总结为一句话,如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的,前半句指的是线程内表现为串行的语义,后半句指的是指令重排序现象和工作内存与贮存同步延迟的现象。举个例子来说明,单例模式的双重检查锁

public class SingletonDemo {

    private /** volatile*/ static SingletonDemo instance;

    public static SingletonDemo getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo.class) {
                if (null == instance) {
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }
}

这里为什么不能缺少volatile关键字呢?主要在于instance = new Instance()这个语句实际上包含了三个操作:
1)分配对象内存空间;
2)初始化对象;
3)设置instance指向刚分配的内存地址

前文中提到一个线程内看其他线程中的指令执行顺序可能是乱序的,有可能是如下顺序:
1)分配对象内存;
2)设置instance指向刚分配的内存;
3)初始化对象
那么其他线程可能取得的是没有初始化的对象,出现诡异的并发bug。

总结

本文主要分析了并发编程中诡异bug的三个来源,可见性问题、原子性问题和有序性问题。

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

推荐阅读更多精彩内容

  • 【韩喜文2019.07.20星期六】 好展馆让天下没有卖不出去的产品 好展馆让天下没有不能传播的文化 日精进:12...
    韩喜文阅读 40评论 0 0
  • 只愿能找一个把我放在第一位的人,不是说对每个人都好,而是对我比对其他人更好,得之,我幸;不得,我命。我只能努力当个...
    风_4922阅读 158评论 0 0
  • 养育孩子是父母一生的修养。 每当土豆出现那么一些不好的行为,第一时间反思:是不是我俩哪里没做好?想尽自己最大的能力...
    沁雨Amy阅读 401评论 1 3
  • ———只因你的一点善意,我愿用一生来守护。 初读只觉得虐,看得难受的虐,以物种间关系作为整个故事的线索,竞...
    橙味的小团糖阅读 389评论 0 2
  • 每个成年人,都不容易,很多时候迫不得已,要戴着面具生活。看起来很开心的样子,看起来活得很好的样子,其实只有自己知道...
    千千玲_688c阅读 329评论 0 2