锁优化

锁优化

  高效并发 是从jdk1.5到jdk1.6的一个重要改进,HotSpot开发团队在这个版本上花费了大量的精力去实现各种锁优化技术:适应性自旋(Adaptive Spinning)、锁消除(Lock Elimination)、锁粗化(Lock Coarsening)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking);

    这些技术都是为了在线程之间更高效共享数据,已解决竞争问题,从而提高程序的执行效率;

自旋锁&自适应自旋  

  互斥同步 对性能最大的影响是阻塞挂起和恢复线程操作都需要转入内核态完成

  虚拟机开发团队 注意到,在很多应用上,共享数据的锁定只会持续很短的一段时间,为了这段时间挂起和恢复线程并不值得;

  如果物理机有一个以上的CPU,能让2个或以上的线程同时并行执行,可以让后面等待锁的线程“等一等”,但不放弃CPU资源,看持有锁的线程是否很快释放锁;

  为了让线程等待,只需要让一个线程执行忙循环(自旋),这项技术就叫自旋锁

  

  自旋锁 在jdk1.4就引入,默认关闭,可以使用-XX:+UseSpinning开启;jdk1.6默认开启;

  自旋等待不能代替阻塞,且 先不说对CPU数量的要求,自旋锁本身虽然避免了线程切换的开销,但要占用CPU资源,如果锁被占用时间很短,自旋等待效果很好;

  反之,自旋的线程会白白浪费CPU资源,不会做任何有用的工作,反而带来性能的浪费;

  自旋等待的时间必须有一定限度,如果自旋等待次数超过限定仍然没有成功获得锁,就应使用传统的方式挂起线程了;

  自旋次数默认是10,用户可以使用-XX:+PreBlockSpin自定义;

 

  jdk1.6引入了 自适应自旋锁

  自适应 意味着 自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者状态来决定;

  如果在同一个锁对象上,上一个线程自旋等待刚刚获得过锁 且 正在运行,JVM认为这次自旋很有可能成功,进而它将允许自旋等待持续更长时间;

  如果对于某个锁对象,自旋很少获得成功,以后获取锁将忽略掉自旋,避免浪费CPU资源;

  有了自适应自旋,随着程序运行和性能监控信息不断完善,JVM对程序锁的状况预测越来越准确;

 

锁消除 

  锁消除JVM即时编译器 在运行时,对一些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进行消除;

  锁消除主要判断依据逃逸分析的数据支持

    (如果在一段代码中,堆上所有数据都不会逃逸出去从而被其他线程访问,可以把他们当做栈上数据对待,认为是线程私有,无需同步加锁)

  

  变量是否逃逸,JVM需要使用数据流分析来确定,

 

锁粗化

  原则上,编写代码的时候,总是推荐将同步代码块的作用范围尽量小-只在共享数据的实际作用域进行同步

  大部分情况下,上面的原则是正确的;

  但如果一系列的操作 对 同一个对象反复加锁/释放锁(甚至加锁出现在循环体中),即使没有线程竞争,频繁进行互斥同步操作也会造成不必要的性能损耗;

  JVM检测到如果有一系列的操作都对同一个对象加锁,会把整个加锁的范围扩展(粗化)到整个操作序列的外部;

 

轻量级锁 

  轻量级锁 是jdk1.6 中加入的新型锁机制;

  “轻量级” 是相对于使用系统互斥量的传统锁而言,因此传统锁机制被称为“重量级”锁;

  轻量级锁 不是用来代替 重量级锁,而是在没有锁竞争的情况下,减少使用传统锁带来的性能损耗;

  

  要理解轻量级锁,必须从Hotspot VM的对象头内存布局说起:

    对象头包括:

      Mark Word(存储对象自身的运行数据 [ hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳 ])、

      类型指针(指向 方法区对象类型数据的指针)、

      [数组还会存储数组的长度]

    对象头信息 是与 对象自身定义的数据无关的额外存储成本,考虑到VM的空间效率,Mark Word被设计为一个非固定的数据结构以便在极小的内存存储更多的信息;

    

    加锁过程

      在进入同步代码块时,如果此对象的没有被锁定(锁状态标志01),VM将在当前线程的栈桢中建立一个lock Record的空间(用于存储同步对象的Mark Word的拷贝);

      

      然后,VM使用CAS操作尝试将同步对象的Mark Word更新为指向Lock Record的指针,如果更新成功,当前线程将持有该同步对象的锁,同步对象的锁状态标志更新为00;

      

      如果CAS操作失败,VM检查同步对象的MarkWord是否指向 当前线程栈桢;

      如果 当前线程已持有锁,直接进入同步块执行;

      如果 被其他线程占用,要膨胀为 重量级锁,同步对象的锁状态标志更新为10,后面等待的线程会进入阻塞;

    解锁过程

      如果同步对象的Mark Word指向当前线程的栈桢,用CAS操作将同步对象的MarkWord和当前线程的LockRecord进行交换;

      如果交换成功,同步过程完成;

      如果交换失败,说明有其他线程尝试获取该同步对象,在释放锁的同时,唤醒被挂起的线程;

      

    轻量级锁提升性能的依据:对于大部分锁,不存在锁竞争关系;

    如果没有锁竞争,轻量级锁使用CAS操作避免使用互斥量的开销;

    如果有锁竞争,除了互斥量的开销,还有CAS操作,所以有锁竞争的情况下,轻量级锁会比传统的锁更慢;

 

偏向锁

  偏向锁 是 jdk1.6引入的一项锁优化;

  偏向锁目的是 消除共享数据在无竞争情况下的同步原语,进一步提高程序的性能;

  如果说 轻量级锁 是在无竞争的情况下使用CAS操作消除同步使用的互斥量偏向锁在无竞争的情况下,消除整个同步(CAS也不做)

  

  偏向锁的意思:偏向第一个获得它的线程,如果没有锁竞争存在,持有该锁的线程将不需要同步;

 

  假设当前VM启用了偏向锁(-XX:+UseBiasedLocking),

  当前同步对象第一次被线程获取的时候,VM会把同步对象的对象头标志位更新为01(偏向模式);

  再使用CAS操作把线程ID 更新到 同步对象的MarkWord中,如果更新成功,该线程进入同步块不需要同步操作;

  

  当另一个线程尝试获取锁时,偏向模式结束,同步对象的MarkWord更新为01(未锁定)或轻量级锁...;

  

  偏向锁 可以提高 带有同步但无竞争的程序性能;

  大多数情况下,锁是被竞争的,偏向模式有点多余,有时候禁用偏向锁可以提升性能(-XX:-UseBiasedLocking);

   

posted on 2023-10-25 14:57  anpeiyong  阅读(11)  评论(0编辑  收藏  举报

导航