并发编程18

并发编程18

synchronized和lock选哪个

  • 性能几乎一样,但是sync是内置锁,由jdk组件的源码可知,sync性能要高于reentrantlock,但是这个高可以忽略

  • 1.6之前 lock高于sync

  • 1.8之后 concurrenthashmap改成了用sync来实现

  • 但是从功能上来说,需要看情况,

    • 1、lock是可中断的(人为中断—lockInterrupted、超时中断)
    • 2、多条件wait(await)
    • 3、公平锁和非公平锁,sync是非公平锁
    • 4、lock也支持读写锁

volatile

  • 解决可见性、指令重排
  • java里面所有的禁止指令重排的原因在哪里—可见性

指令重排

  • 编译器级别

    • 看到字节码重排后的结果
  • CPU指令乱序执行

    • CPU为了减少跟缓存、内存的交互,只跟寄存器交互,会进行指令重排

    • a=2;
      b=4;
      // 这种重复取值赋值,但是没依赖关系的可以重排
      a++;
      // 这种有依赖关系的不能进行重排
      c=a+b;
      
  • 内存级别引起的乱序

    • 相当晦涩

    • 发生的根本原因?

      • 严格意义上讲是内存屏障—mesi(interl x86)

        • 不同core之间的线程存在某种同步,某一个core里面的线程会先去别的core里面的缓存看是否有自己要找的值,有的话就不去主存中查找了

        • 不同core之间的缓存中某一个向另一个做同步时,像这种同步一般是通过异步实现的,其中有一个叫做store buffer屏障

          • package BingFaBianCheng.bingFaBianCheng17.jmm;
            
            import lombok.SneakyThrows;
            import lombok.extern.slf4j.Slf4j;
            
            import java.util.concurrent.TimeUnit;
            
            /**
             * @Author 钢牌讲师--子路
             **/
            @Slf4j(topic = "e")
            public class Test7 {
            
                int a = 0;
                int b = 0;
            
                public static void main(String[] args) {
            
                }
            
                /**
                 * a---在t2的cache中
                 * b---在t1所在的cache中
                 * t1---所在的cache中
                 */
                //t1
                public void xx(){
                    /**
                     * 1 拿到a---由于t2已经cache了a  直接从t2获取a
                     * 2 a=2 Intel公司追求性能,通过异步的方式共享数据  store buffer
                     *   而不是立即将a的值从core写到cache中
                     * 3 除了2,还有另一种可能,牺牲性能
                     *   可能a=2:先告诉t2 同时将t2中的缓存由a=0改成a=2 再通知t1
                     */
                    //t1修改完之后需要同步(mesi规定的)t2
                    //由于store buffer的存在,不会立马同步,先存到sb中等着
                    a=2;
                    //不需要同步给其他CPU的
                    b=1;
                }
            
                //t2
                public void aa(){
                    //todo -1000---a=0
                    //执行这1000行代码的时候线程切换去执行xx()方法
                    //此时xx()方法执行完,再切换回aa()方法
            
            
                    //因为1000行代码中没有缓存b
                    //它会从哪里拿数据---t1的cache当中拿b
                    //为什么不从主存中拿b,而是从t1?
                    //追求性能,先看缓存有没有,有就拿,没有才去查主存
                    //t1中执行了b=1,但是a=2没有同步到t1的cache
                    while(b!=1){
                        break;
                    }
                    //假的,输出false,这个时候 它会直接从t2的cache当中去拿a=1
                    //相当于上面的a=2;b=1;
                    //最终的结果看上去b=1先执行
                    //看上去好像是发生了指令重排,即指令重排是表现出来的结果
                    //解决方法,加一个volatile
                    assert(a==2);
                }
            
            
            
            }
            
            
        • mesi指数据的4种状态

        • 保证多核CPU下面的缓存一致性

      • 有了mesi为什么还有可见性问题?

        • 因为不是直接存储到cache中,而是通过异步的方式,先写到store buffer中,导致不是立马可见的,有一定的延迟性(见open jdk源码)
          • 如果要解决store buffer的可见性问题,store buffer中的指令a=2,会在store buffer加一个写的屏障,然后是b=1
          • 通过在指令之间加一堵墙,不让它重排
        • 如果没有store buffer就没有可见性问题了,但是效率就降低了,
      • mesi的强一致性和弱一致性?

volatile怎么加内存屏障的

volatile如何禁止指令重排,保证可见性的?

  • package bingFaBianCheng17.jmm;
    
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * @Author 钢牌讲师--子路
     **/
    @Slf4j(topic = "e")
    public class Test8 {
    
        static int a = 0;
    
        //
        public static void main(String[] args) {
            new Thread(()->xx()).start();
        }
    
        public static void xx(){
            a=1;
        }
    }
    
    
  • 方法里面—xx()汇编,不管a变量加不加volatile,xx()方法的汇编结果都是一样的

    • 0 iconst_1
      1 putstatic #6 <bingFaBianCheng17/jmm/Test8.a>
      4 return
      
      
  • 字段里面—a,flags里面多了一个volatile的标识

    • // 0x0048是volatile固定的
      Access flags:0x0048[static volatile]
      
    • Access flags:0x0008[static]
      

内存屏障更详细的解释

  • package bingFaBianCheng17.jmm;
    
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * @Author 钢牌讲师--子路
     **/
    @Slf4j(topic = "e")
    public class Test8 {
    
    //    static int a = 0;
        volatile static int a = 0;
    
        //
        public static void main(String[] args) {
            new Thread(()->xx()).start();
        }
    
        public static void xx(){
            /**
             * jvm汇编指令是不会变的
             *
             * a=1---机器码是不是一样的?
             *
             * 如何看到字节码指令所生成的汇编
             *
             * 需要下载hsdis-amd64.dll,并解压,把它复制到jre中bin目录下,
             * 并且edit configurations,选择jre是刚才的路径,
             * -server
             * -Xcomp
             * -XX:+UnlockDiagnosticVMOptions
             * -XX:+PrintAssembly
             * -XX:CompileCommand=compileonly,*Test8.xx()
             *
             * loadload
             * storestore
             * loadstore
             *
             * // Hotspot---源码---遇到了volatile关键字 操作--->storeload()---会在原先汇编代码
             * // 的基础上加一个lock,lock实际不是屏障,相当于屏障,比屏障更强
             *
             * // 拓展:jvm执行代码的引擎
             * // 1.字节码引擎---sync(对c++代码解释执行,bytecodeInterpreter.cpp)
             *    - if(cache->is_volatile()) --- 判断缓存中是否被volatile修饰
             *    - 然后判断类型if(tos_type == itos)....一系列类型
             *    - 里面有一行代码OrderAccess::storeloader
             *    - 然后跳到fence()方法
             *    - __asm__ volatile("lock;addl $0,0(%%rsp)"...) --- 此处的volatile是c++中的,防止gcc编译器去优化掉无用的代码
             *    - gcc -O0 -O1 -O2 -O3 --- 加了volatile就会组织gcc优化
             *    - addl $0,0(%%rsp)是一个不用操作,addl是加,在寄存器里面加两个0,是一个空操作,防止脏读问题
             *    - 如果不加volatile会被过滤掉,lock是锁总线
             *    - jvm是通过storeload实现内存屏障,hotspot中实现storeload是通过锁总线的方法
             *    - 锁总线是什么意思?
             *         --- a.cpu不能执行代码,空闲下来了,然后去做不同core之间的缓存同步
             *         --- b.同时只有锁总线的core可以往主存中写,也就是串行的
             * // 2.模板引擎(逻辑是一样的,但是不会对字节码进行解释,为了效率更快,直接转成汇编了(机器码),不会有中间结果)
             * // 3.jit,字节码执行一次,解释一次,执行1000次后,就不会解释了,直接转成汇编(为什么又提到了jni?)
             *
             * 为什么这个lock add这把锁可以实现内存屏障?
             *
             * storeload(jmm级别术语---抽象---标准) // 性能最慢,相当于其它3个屏障都不用加了
             *
             * 四个屏障的力度?
             */
            a=1;
            System.out.println("------------------");
        }
    }
    
    

查看汇编的命令

  • -server
    -Xcomp
    -XX:+UnlockDiagnosticVMOptions
    -XX:+PrintAssembly
    -XX:CompileCommand=compileonly,*Test6.xx()
    

保护性暂停模式

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值