synchronized锁的膨胀过程

  在jdk1.6以前,随着并发数提高,synchronized吞吐量下降严重,而ReentrantLock则比较稳定,如果说ReentrantLock性能较强,那么synchronized则有非常大的优化空间。而在JDK1.6发布后,两者性能基本持平。因此,性能问题不再是选择ReentrantLock的理由,虚拟机在未来性能改进中肯定也会更加偏向原生的synchronized,在synchronized能实现需求的情况下,推荐优先使用synchronized。

  synchronized同步锁共有4中状态:

  • 无锁
  • 偏向锁
  • 轻量级锁
  • 重量级锁

  其随着竞争激烈程度的升级,synchronized锁开始膨胀:无锁-->偏向锁-->轻量级锁-->重量级锁。而在这个过程中,JDK1.6对锁进行了优化。

锁优化

自旋锁与自适应自旋

  互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程都需要转入内核态中完成,给系统的并发能力带来很大压力。

  实际操作发现,大部分场景在加锁后执行操作时间都很短,就开始释放锁。如果能让后面需要锁的线程稍微等一下,不去进行挂起操作,那它大概率很快就能得到锁,避免线程开销。这个等一下就叫自旋(执行一个忙循环)。

  如果自旋时间过长,还没有获得锁,反而白白浪费时间片,因此默认的自旋会有一个自旋次数10.

  JDK1.6引入了自适应自旋锁。其自旋次数根据上一次在同一个锁上的自旋时间来决定。如果在同一个锁,上一个线程刚刚获得过锁,并且正在运行中,则虚拟机会认为这次自旋大概率会成功,则会自动允许更长时间的自旋;如果某个锁,加锁时间过长,很少有线程可以通过自旋等到它,则以后获取这个锁的自旋过程可能被省略,以免浪费处理器资源。

锁消除

  在一些虽然写了同步,但实际发现不可能存在竞争关系的场景,虚拟机会自动取消同步过程。

锁粗化

  如果一系列操作对同一个对象反复的加锁解锁,甚至加锁出现在循环体内部,本没有线程竞争,导致了不必要的开销。

  虚拟机会把加锁的同步范围扩展(粗化)到整个操作序列的外部,这样仅加锁一次即可。

膨胀过程

偏向锁

  【只有一个线程竞争锁】

  JDK1.6引入的一项锁优化,偏向于第一个获得它的线程。

  多数场景下,锁没有竞争关系,总是由同一个线程多次获取,若该锁一直没有被其他线程获取,则持有偏向锁的线程将无需加锁。直到另一个线程到来,偏向锁开始膨胀。

  如果程序中大部分情况有多个线程访问锁,那偏向模式比较多余。

轻量级锁

  【多个线程交替竞争锁】

  JDK1.6加入的新型锁机制。其使用CAS操作尝试更新对象,若更新成功了,这个线程就拥有了锁,此时就处于轻量级锁定状态;若更新失败了,开始自旋,自旋一定次数CAS操作依然没有成功,则判断拥有锁的是否为当前线程,如果是就直接进入同步代码块执行,如果不是,则说明两个线程在争夺锁。如果争夺锁的线程在2个以上,轻量级锁就要膨胀为重量级锁。

  如果没有竞争,轻量级锁使用CAS避免了互斥开销,但如果存在多个线程的锁竞争,就要转换为重量级锁,因此在互斥的情况下还多了CAS操作,竞争激烈情况下,轻量级锁比重量级锁更慢。

重量级锁

  【多个线程同时竞争锁】

总结

  偏向锁、轻量级锁均为乐观锁,重量级锁为悲观锁。

  • 最初加锁的对象没有线程访问,为无锁状态。当第一个线程来访问他,他偏向第一个线程,此时对象持偏向锁。
  • 第二个线程来,发现该锁已经是偏向锁,检查原来拥有锁的线程是否存活,如果挂了,则先置为无锁状态,然后锁偏向新的线程,如果没挂,则升级(膨胀)偏向锁为轻量级锁
  • 轻量级锁认为竞争存在,但竞争不激烈,只有两个线程,一般两个线程的竞争都会错开,或者在等待时简单自旋一下就能获得锁。但自旋超过一定次数,或者第三个线程来时,需要三个线程竞争锁,那么此时轻量级锁就会升级(膨胀 )为重量级锁。
  • 锁的膨胀过程是不可逆的(偏向锁可以被重置为无锁状态)
  • 偏向锁和轻量级锁在用户态维护,重量级锁需要切换到内核态维护(因为只有内核态才可以阻塞和唤醒线程)

你可能感兴趣的:(Java,并发与多线程,synchronized,多线程,锁,膨胀过程)