并发编程-Volatile解决JMM的可见性问题

上一篇 <<<Java内存模型(JMM)
下一篇 >>>Volatile的伪共享和重排序


Volatile的特性

  • a、保证变量对所有线程的可见性
  • b、禁止指令重排序优化------相当于一个内存屏障,不让顺序优化
    (指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)
    他不能够保证共享变量的原子性问题

CPU多核硬件架构剖析

CPU每次从主内存读取数据比较慢,而现代的CPU通常涉及多级缓存,CPU读主内存。
按照空间局部性原则加载局部快到缓存中,缓存速度会快很多。

产生可见性的原因

因为我们CPU读取主内存共享变量的数据时候,效率是非常低,所以对每个CPU设置对应的高速缓存 L1、L2、L3 缓存我们共享变量主内存中的副本。
相当于每个CPU对应共享变量的副本,副本与副本之间可能会存在一个数据不一致性的问题。
比如线程线程B修改的某个副本值,线程A的副本可能不可见,最终导致了可见性问题。

Volatile的代码展示

添加的dll文件
X:\java8\jdk\jre\bin\server 放入 hsdis-amd64.dll

/**
 * 在启动的VM Options里加入:-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*JaryeThread.*
 * 即可查看volatile关键字使用的锁信息:
 0x000000010cf75c13: lock addl $0x0,(%rsp)     ;*putstatic flag
 ; - com.jarye.JaryeThread::<clinit>@1 (line 18)
 *
 *
 0x000000010cf751cf: lock addl $0x0,(%rsp)     ;*putstatic flag
 ; - com.jarye.JaryeThread::main@21 (line 35)
 **/
public class JaryeThread extends Thread {
    // 1.能够保证可见  当一个线程在修改我们主内存共享变量的数据 对另外一个线程可见
    /**
     * 汇编指令lock锁的机制 能够把让工作内存主动刷新主内存数据
     */
    private volatile   static boolean flag = true;
//
    @Override
    public void run() {
        while (flag) {
        }
    }

    public static void main(String[] args) {
        // 默认的情况下创建的线程为用户线程
        new JaryeThread().start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {

        }
        // 主线程
        flag = false;
        System.out.println("主线程已经停止啦~~...");
    }
}

Volatile的底层原理

通过汇编lock前缀指令触发底层锁的机制,帮助我们解决多个不同cpu之间三级缓存之间数据同步
锁的机制两种:总线锁/MESI缓存一致性协议

总线锁【维护解决cpu高速缓存副本数据之间一致性问题】

当一个cpu(线程)访问到我们主内存中的数据时候,往总线发出一个Lock锁的信号,其他的线程不能够对该主内存做任何操作,变为阻塞状态。
该模式存在非常大的缺陷,就是将并行的程序变为串行,没有真正发挥出cpu多核的好处。

MESI缓存一致性协议

  • M修改 (Modified) 这行数据有效,数据被修改了,和主内存中的数据不一致,数据只存在于本Cache中。
  • E 独享、互斥 (Exclusive)这行数据有效,数据和主内存中的数据一致,数据只存在于本Cache中。
  • S 共享 (Shared)这行数据有效,数据和主内存中的数据一致,数据存在于很多Cache中。
  • I 无效 (Invalid) 这行数据无效。

A、主内存flag=true,thread-main和thread-A两个线程都为S
B、主内存设置flag=false时,thread-main设置为M
C、主内存在做写操作时,thread-main设置为E,写成功后变为S,然后总线嗅探机制将thread-A变为I
D、Thread-A从主内存中主动拉取flag的值到工作内存中,成功后设置值为S。

Volatile为什么不能保证原子性


/**
 * volatile为什么不能保证原子性:
 * 线程1、线程2同时执行count++的操作,本地内存都做了变更,
 * 由于使用MESI协议,假设线程1的执行到主内存中时,总线嗅探器告诉线程2主内存已更改,直接将线程2的数据改为失效,从主内存中读取,导致了线程2的首次count++失效
 * 然后线程2再执行count++的操作,刷新到主内存中
 * 结论:线程2的首次count++失效,导致总体count的数据出现了偏差
 *
 **/
public class VolatileAtomThread {
    // 共享的全局变量
    private  volatile static int count = 0;

    /**
     * 一定会小于10000
     */
    public static void create() {
        count++;
    }

    public static void main(String[] args) {
        ArrayList<Thread> threads = new ArrayList<>();
        // 10次 10个线程 每个线程执行1000次
        for (int i = 0; i < 10; i++) {
            Thread tempThread = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    create();
                }
            });
            threads.add(tempThread);
            tempThread.start();
        }
        threads.forEach((t)-> {
            try {
                // 主线释放锁同时放弃cpu执行前 等待前面10个线程执行完毕
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 100001 10002
        System.out.println(count);
    }
}


相关文章链接:
<<<多线程基础
<<<线程安全与解决方案
<<<锁的深入化
<<<锁的优化
<<<Java内存模型(JMM)
<<<Volatile的伪共享和重排序
<<<CAS无锁模式及ABA问题
<<<Synchronized锁
<<<Lock锁
<<<AQS同步器
<<<Condition
<<<CountDownLatch同步计数器
<<<Semaphore信号量
<<<CyclicBarrier屏障
<<<线程池
<<<并发队列
<<<Callable与Future模式
<<<Fork/Join框架
<<<Threadlocal
<<<Disruptor框架
<<<如何优化多线程总结

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

推荐阅读更多精彩内容