多线程与高并发(二)--synchronized,volatile,CAS

268 阅读2分钟

synchronized底层

  • JDK早期的实现是重量级锁(向操作系统申请锁)。
  • Hotspot实现(锁升级)
    • 首先在锁的markword上记录第一个访问的线程ID,并没有上锁(偏向锁)。
    • 如果有线程争用,升级为自旋锁。
    • 默认旋10次之后升级为重量级锁,未获得锁的线程进入等待队列。
    • 可参考ifeve.com/java-synchr…

引申:

  • 在Hotspot锁只能升级,不能降级。
  • 自旋锁只占用CPU,不访问操作系统。在用户态解决锁问题,不经过内核态,加解锁效率比重量级锁效率高。
  • 执行时间长以及线程多的时候尽量使用重量级锁。

synchronized优化

  1. 锁细化:同步代码块中的语句越少越好。
  2. 锁粗化:如果一个方法中加了很多锁,这时把整个方法上锁性能会提高很多。
  3. 锁对象要加final。

volatile

  1. 保证线程可见性

    • 所有线程共享堆内存,另外每个线程都有自己的工作内存。当线程要访问堆内存中的数据时,会把堆内存中的数据copy到线程的工作内存,堆该变量的改变是在工作内存的数据进行改变。线程堆变量的更改,没有及时的反应到其他的线程,导致线程之间数据不可见。
  2. 禁止指令重排序

    • CPU为了提高执行效率,会并发的执行指令。使用volatile之后,CPU不会进行重排序。
      • DCL单例
      package designPatterns;
      public class DCLSingleTon {
      
          private volatile static DCLSingleTon dclSingleTon;
      
          private DCLSingleTon() {
          }
      
          public static DCLSingleTon getInstance() {
              // 外层的判断不能去掉,可以显著提高效率
              if (dclSingleTon == null) {
                  synchronized (DCLSingleTon.class) {
                      if (dclSingleTon == null) {
                          /**
                           * 经过编译之后分为三步
                           * 1.分配内存,给变量赋默认值
                           * 2.成员变量初始化
                           * 3.把内存赋值给dclSingleTon
                           * 如果发生指令重排序,可能会发生2和3交换
                           * 这时dclSingleTon已经不是null,新来的线程就会直接获得未初始化成员变量的instance
                           */
                          dclSingleTon = new DCLSingleTon();
                      }
                  }
              }
              return dclSingleTon;
          }
      }
      
  3. 不能保证原子性

CAS(Compare And Swap)

  • cas(V, Expected, NewValue)

    if V == Expected
    	V = NewValue
    	otherwise try again or fail
    
    • concurrent包中的cas是CPU原语支持,执行过程不会被打断
  • ABA问题

    • 当前线程的CAS操作无法分辨当前V值是否发生过变化。比如在你非常渴的情况下发现一个盛满水的杯子,一饮而尽之后再给被子里重新倒满水。你离开之后,杯子的主人回来看到杯子还是盛满水,他无法确定这杯水是否被人喝过重新倒满。
    • 可以通过加版本号解决。
  • LongAdder与AtomicLong比较

    • 两者都是使用的CAS操作来进行增减。
    • 不通电在于LongAdder内部是分段锁。