六、锁优化

  高效并发是jdk1.5到1.6的一个重要改进,hotspot虚拟机开发团队在这个版本上花费了大量精力去实现各类锁优化技术,例如:适应性自旋、锁消除、锁粗化、轻量级锁跟偏向锁等,这些技术都是为了在线程间更高效的共享数据,以解决竞争问题,从而提升程序执行效率。
自旋锁与自适应自旋:
  虚拟机开发团队注意到,在许多应用上共享数据的锁定状态只会持续很短的时间,为了这段时间去挂起跟恢复线程并不值得(线程的内核态跟用户态转换消耗较高),若是物理机有一个以上的处理器,咱们可让后面请求锁的线程不要放弃cpu的执行时间,而是“稍等一下”,看看持有锁的线程是否会很快释放锁;这个“等一下”通常会实现为一个for循环(自旋),这就是所谓的自旋锁。
  但for循环的时候也会消耗cpu性能!!若是前一个线程很快释放了锁还好,若是执行较慢,在等待的线程一直自旋,对cpu也是一种很大的消耗,所以自旋要有条件限制,通常默认次数为10次,自旋10次没法获取锁,则挂起线程。由于等待时间不可控的状况,jdk1.6引入了自适应的自旋锁。就是jdk会根据本身等待的“经验”来决定自旋时间;若是以前的等待都成功了,则认为本次执行也极可能成功,等待时间会适当延长。
锁消除:
  虚拟机即时编译器在运行的时候,会对一些代码上要求同步,但被检测到不可能存在同步的代码进行锁消除。锁消除的主要判断依据来源于逃逸分析的数据支持,若是判断在一段代码中,堆上的全部数据都不会逃逸出去从而被其它线程访问到,那就能够把它们当作栈上的数据对待,认为他们是线程私有的,同步加锁无需进行。
例如:
public String concatString(String s1, String s2, String s3){
  StringBuffer sb = new StringBuffer();
  sb.append(s1);
  sb.append(s2);
  sb.append(s3);
  return sb.toString();
}
  StringBuffer的append是同步的,但通过分析,咱们发现它的动态做用域被限制在concatString方法中,相关变量也都在线程执行的jvm栈中,其它线程没法获取,所以,虽然这里有锁,但能够安全的消除掉,即时编译器编译以后,这段代码就会忽略掉全部的同步而直接执行了。
锁粗化:
  写代码的时候,咱们老是要把同步的范围设置的尽可能小,以使得须要同步的代码量尽可能变小,执行速度快,而让其它线程尽快拿到锁。
但若是一系列的操做都是对同一个对象反复加锁跟解锁,甚至加锁操做在一个循环中,会致使即便没有竞争,也会由于频繁加解锁而产生没必要要的性能损耗。例如上例的concatString方法有三个append都是对同一对象加锁,其实只须要一次就够了;若是jvm探测到这样的代码,会把加锁同步范围扩展(粗化)到真个操做序列的外部(固然,只是举例说明,实际这里最终会由于没有竞争而被消除锁)。
轻量级锁:
  轻量级锁不是用来代替重量级锁的,它的本意实在没有多余线程竞争的前提下,减小传统的重量级锁由于使用操做系统互斥量而产生的性能消耗。
jvm中java对象的对象头包含4部分:hashCode,gc分代,锁标志位,固定位0。
  轻量级锁就是采用一种乐观锁的机制,对对象的锁标志位进行“抢占”,成功,则将锁标志位设置为一个指向线程执行栈的指针(所指向的栈帧存放原来锁标志位的内容),失败则检查是否当前线程持有该对象锁,是则继续执行,不然说明发生了多线程锁竞争,升级为正常的重量级锁。
  轻量级锁采用cas操做,避免了互斥量的开销,相对来讲比较轻便,但若是竞争较为频繁的话,轻量级锁不但升级为了普通的重量级锁,并且还额外多了cas的操做,会致使轻量级锁比重量级锁更慢。
偏向锁:
  偏向锁的目的是消除代码在无竞争的状况下的同步操做,进一步提升程序的性能。若是说轻量级锁是在无竞争的状况下使用cas操做去代替互斥量,那偏向锁就是在无竞争的状况下,干脆把整个同步都消除掉,连cas操做都不做了。
  偏向锁的“偏”,就是偏爱的偏,意思是这个锁会偏向于第一个得到它的线程,若是在接下来的执行过程当中该锁没有被其它线程获取,则持有偏向锁的线程将永远不须要再进行同步。实现原理跟轻量级锁同样,也是经过修改对象头信息中的锁标志位进行的。
  当有另外一个线程尝试获取这个锁时,偏向模式就宣告结束,根据锁对象目前是否处于被锁定状态,撤销偏向后恢复到未锁定状态,或者轻量级锁定状态。同轻量级锁同样,偏向锁也是一个带有效益权衡性质的优化,若是程序中大多数的锁老是存在多个线程的竞争访问,那偏向锁就是多余的,这时候禁用掉偏向反而会提高性能。
相关文章
相关标签/搜索