多线程与高并发编程(二)

  • volatile

    • 保证线程可见性

      • java有堆内存,大家共享堆内存,当然每个线程也有自己的工作空间,当需要数据的时候copy到自己的工作空间,如果修改了会出现与堆数据不一致,就就是说对公共数据的更改另一个线程不可见,如果加上volatile,就是强制每次访问数据都会被强制推回堆,底层是cpu的缓存一致性协议保证的
    • 禁止指令重排序

      • 什么是指令重排序?

        • 是cpu为了提高速率将指令进行了重排序,效率提升了很高
      • volatile会涉及到经典的单例问题

        //饿汉
        public class single{
          //static数据交给jvm初始化,静态变量只会new出一个实例
          private static final single INSTANCE=new single();
          //防止外界new
          private single(){}
          
          public static single getInstance(){
              return INSTANCE;
          }
        
        }
        //懒汉-线程不安全
        public class single{
          //static数据交给jvm初始化,静态变量只会new出一个实例
          private static single INSTANCE;
          //防止外界new
          private single(){}
          
          public static single getInstance(){
              if(INSTANCE==null){
                  INSTANCE=new single();
              }
              return INSTANCE;
          }
        }
        //DCL-线程安全
        public class single{
          //static数据交给jvm初始化,静态变量只会new出一个实例
          private static volatile single INSTANCE;
          //防止外界new
          private single(){}
          
          public static single getInstance(){
              if(INSTANCE==null){
                  synchronized(this.class){
                      if(INSTANCE==null){
                          INSTANCE=new single();
                      }
                  }
              }
              return INSTANCE;
          }
        }
          
        
        • 为什么DCL要加volatile,问题出现在INSTANCE=new single();

          这句指令new一个对象分为三步

          • 第一步是申请内存大小,并且会赋一个初始值
          • 第二步赋上真正的值
          • 第三步把内存的东西指向引用名INSTANCE

          如果超高的并发情况下,指令重排发生把第三步挪到了第二步,如果线程A执行了一三步,这时INSTANCE不为空但是值不正确,另一个线程同时进入判断INSTANCE不为空直接拿去用了,这就出现了错误

        • volatile保证了有序性和可见性,但是不能保证原子性,所以说他不能替代synchronized

        • 锁细化和锁粗化,细化是尽量把加锁的代码片段缩小,那什么时候用锁粗化?就是如果一个对象被分成了好多小锁,这样加锁频繁,不如粗化成大锁

  • CAS(无锁优化,自旋,乐观锁)Compare And Set

    • AtomicXXX都是线程安全的原子类
    /*volatile int count=0*/
    AtomicInteger count=new AtomicInteger(0);
    
    /*synchronized*/void m(){
      for(int i=0;i<1000;i++){
          count.incrementAndGet();
          /*count++*/
      }
    }
    

    count.incrementAndGet();底层就是Unsafe的weakCompareAndSet()方法

    cas(V,Expected,NewValue){
      if V==E
      V==NewValue;
      otherwise try again or fail
    }
    
    • 如果在if判断过程中出现把V给改了怎么办?

      CAS是CPU原语支持,不会存在这种情况

    • ABA问题?

      • CAS期望是A,在CAS没操作的时候,另一个线程先改成了B又改成了A
      • 对于基础数据类型似乎没有影响,但是如果是对象呢?
        • 由于对象判断的是引用地址是否改变,而里面的内容是不知道的,如果在对象指向B的时候,B的代码对A对象里的内部进行了修改,那么当在置换为A的时候,cas仍然认为A没有发生任何变化,只是因为他判断的是引用,指向的确实是A,就比如和前女友复合的过程,你不知道前女友在符合之前又经历了多少个男人,但是前女友还是那个人
      • 解决办法加版本号CAS(version),可以用AtomicStampedReference解决(知道就行)
    • 简单看看Unsafe

      • 所有原子类都是通过unsafe的CAS实现的
      • 用来分配内存的,类似于malloc
      • unsafe类是单例的
      • 1.8版本Unsafe不能获取,,而1.9版本可以get拿到,之前可以反射获取,但是现在1.9版本即使获取,代码能写出来,也运行不了,应该是作为内部api了,把这个接口给关了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容