理解jvm之多线程数据一致性问题

  关于多线程的文章非常多,但是有一个特点,大多数文章都只讲一个或几个点,并没有真正把知识串联起来,没有达到融汇贯通的效果。如何保证多线程操作的一致性问题,为何要用volatile,为何要用synchronized呢?我们要知其然,更要知其所以然。
  多线程操作,就是多核cpu一起操作某个共享变量,这个操作如果不进行规范控制,那结果是不可预知的,也就是线程不安全,那么jvm是怎么来保证线程安全呢?下面用比较通俗的方式分为两类,可能这样分不太确切,但便于理解。
一、变量级别的一致性
volatile关键字
  cpu执行时需要将数据从主内存读取到工作内存(缓存)中执行,而cpu的工作内存是每个核都有对应的独立内存空间,参与cpu运算的数据都会从主内存加载到工作内存进行操作。假如有两个线程,一个线程在修改工作内存的数据,另一个线程同时在主存读取数据,如果没有特殊规则,很可能会造成读取数据时读到了没有更新的数据,这就是不可见性。

可见性问题.png

  出现以上问题,就是因为i=6虽然在工作内存已经变了,但是不会立即刷新到主存中。为了解决缓存不一致性问题,通常硬件层面有以下两种解决方法:
1.通过在总线加 LOCK# 锁的方式
2.通过缓存一致性协议
第一种方式:通俗理解就是某一核cpu的独占,可以比喻成排队,当某一核cpu在操作时会发出一个LOCK信号,在总线上锁定,等数据刷到主存了再由另一核cpu操作该数据。这种方式效率是比较低的,现代的cpu一般不会采用。

第二种方式:就是规定如果操作的变量是多线程共享的,那么得遵循以下规则,
1.写操作时必须立即把数据刷新到主存,并通过其它核的cpu把对应工作内存的数据变为失效。
2.读操作时直接从主存读取。
Intel 的 MESI 协议就是解决这个问题的。

  但是java是号称可以在所有环境中执行的,那就必须争对这个问题有一个好的解决方案,这个方案就是volatile关键字实现的内存屏障
内存屏障分为两种:
Load Barrier 读屏障
Store Barrier 写屏障
有两个作用
1.写的时候,强制把缓冲区/高速缓存中的数据写回主内存,并让缓冲中的数据失效;读的时候直接从主内存中读取。这个思路与Intel 的MESI很像,但这是程序级的实现。
2.阻止屏障两侧的指令重排序
指令重排又是怎么回事呢?指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序。(这部分内容后面描述)
  那么问题来了,使用了volatile关键字就可以解决线程安全问题了吗?答案当然是不行的,为何呢?因为变量共享操作的一致性是解决了,但程序级别的一致性没解决。

线程安全问题.png

  所以如果将变量i只用volatile关键字修饰,多线程编码时是不能解决线程安全问题的。

CAS操作
  CAS操作是多线程经常用到的操作,CAS:Compare and Swap,即比较再交换。我们熟悉的AtomicInteger、synchronized、Lock都是依赖CAS操作进行的。CAS与volatile配合可以保证多线程的数据一致性,因为CAS需要两个数据,一个旧值,一个新值,如果现在主内存中的值与旧值相等,那么就直接把旧值换成新值,否则不交换。概括一下,CAS其实就是乐观锁的思路,我要改一个值,前提是这个值必须等于我之前拿到的值,否则我不能改。
  CAS是原子操作,也就是说执行比较交换这个指令是一条不可分割的指令,这是因为CPU硬件直接支持比较交换操作,所以这样的操作是线程安全的。简单说说AtomicInteger、synchronized、Lock是如何利用CAS操作的:
AtomicInteger:
  这是原子累计操作,不需要上锁就可实现多线程的累加功能。其实现的本质就是volatile+CAS,volatile保证了多线程之间的数据可见即可获取最新的数据,而CAS把获取到最新的数据进行+1。如果此时另一个线程先+1了,则这次的操作会失败,会重新获取到新的值继续尝试+1直到成功。
synchronized、Lock:
  原理是通过CAS操作去设置一个值,如果设置成功说明拿到了锁,如果设置失败那就会进行短时间等待后再偿试设置,如果还不成功则转到等待队列。锁的操作离不开volatile+CAS,锁的具体实现需要专门写一篇文章讲解,此处不再深入。

二、程序级别的一致性

先来看看下面这个经典的双重检查锁实现单例:

class Singleton{
    private static  volatile Singleton singleton;
    private Singleton(){};
    public static Singleton getInstance(){
        if(singleton == null){
            synchronized(Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();   
                }
            }
        }
        return singleton;
    }
}

为何要写一个单例要这么复杂呢?因为存在两个问题:
1、多线程操作同一个变量
2、指令重排序

说说指令重排序:
问题出现在创建对象的语句singleton = new Singleton(); 上,在java中创建一个对象并非是一个原子操作,可以被分解成三行伪代码:

//1:分配对象的内存空间
memory = allocate();
//2:初始化对象
ctorInstance(memory);  
//3:设置instance指向刚分配的内存地址
instance = memory;     

上面三行伪代码中的2和3之间,可能会被重排序(在一些JIT编译器中),即编译器或处理器为提高性能改变代码执行顺序,重排序之后的伪代码是这样的:

//1:分配对象的内存空间
memory = allocate(); 
//3:设置instance指向刚分配的内存地址
instance = memory;
//2:初始化对象
ctorInstance(memory);

  如果不进行锁定检查,在多线程获取单例时很可能获取到一个指向了内存地址但是没有初始化的对象。singleton对象是用volatile修饰的,volatile可以保证不进行指令重排序。另外在类对象上加了synchronized锁,保证了同一个时间只能由一个线程执行代码块的new操作,这样就保证单例不会出空指针问题。当然一般情况下建议直接通过饿汉模式直接new一个静态类,也能保证单例,因为静态类型是在类加载的时候已经初始化好了。

以上就是对jvm保证多线程的数据一致性的一些理解。

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