synchronized的优化——自适应自旋、偏向锁

到JDK1.6,对synchronized加入了很多优化措施,如:自适应自旋、锁消除、锁粗化、偏向锁、轻量级锁等。

在说synchronized内建锁的优化之前,先来了解两个小的知识点:

1.CAS操作:

我们使用synchronized加锁时,是一种悲观锁策略,即认为每一次获取锁时都会发生冲突。若当前线程获取到锁,则会阻塞其他想要获取锁的线程。

而CAS操作,又称无锁操作,是一种乐观锁策略,即认为多个线程访问共享资源时不会出现冲突。如果出现冲突,就不断的比较交换直到没有冲突为止。

(1) CAS操作过程:

说到过程,不得不先了解一下这其中包含的三个值,V——内存地址中实际存放的值、
O——预期的值,也是旧值、N——新值,用来更新数据。

  • 若V == O,则表示该值没有被其他线程修改过,此时,V = N,更新V值。
  • 若V != O,则表示此时内存中的值已经被其他线程修改过,此时返回V值即可。
  • 当多个线程使用CAS操作时,只有一个线程可以成功,失败的线程会重新尝试或者挂起。

(2) CAS的问题:

  • ABA问题:若V值原本为1,然后线程将这个值改为2,又改回为1,这样看起来好像是没有发生变化,实际上发生了变化。若线程此时修改了这个值,则会导致其余几个线程出现异常。我们采用添加版本号的方法来解决ABA问题。
  • 自旋问题:若一个线程没有拿到锁,synchronized会让它阻塞,而CAS操作则会不断尝试,这样的自旋会浪费大量的处理器资源。为了解决这个问题,JVM的解决方案是自适应自旋,即根据以往的自旋等待时能否获取到锁,来动态调整自旋的时间。若上次在一定时间内自旋而获取到了锁,则这次可以加长自旋时间;反之,则减少自旋时间。
  • 不公平的锁机制:处于阻塞状态的线程,无法立即竞争被释放的锁;而处于自旋状态的线程,很有可能优先获得锁。内建锁无法实现公平锁机制,而Lock体系可以。

(3) CAS与synchronized的区别:

  • 使用synchronized加锁,若当前线程获取到锁,则其他想要获取锁的线程会被阻塞,直到锁释放才被唤醒,这样会降低性能,因为这是一种互斥同步,即阻塞同步。
  • 使用CAS获取锁失败后,并不会阻塞线程,而是会进行一定的重试,即自旋操作,这是一种非阻塞同步。

2.对象头:
Java对象由对象头、对象体以及对齐字节所组成。而Java对象头中的Mark Word默认存放的是对象的hasCode、分代年龄以及锁标记位等。稍后我们将以64位Mark Word为例,来研究synchronized内建锁的几种状态。


在JavaSE 1.6中,锁一共有四种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁、重量级锁。

这几个状态会随着锁的竞争逐渐升级,锁可以升级但不能降级,这是为了提高获得锁和释放锁的效率。

1.无锁:即此时不存在任何竞争
无锁状态下的64位Mark Word:
在这里插入图片描述
2.偏向锁:针对从始至终只有一个线程获取同一把锁
(1) 偏向锁的获取:

  • 当一个线程第一次获取锁时,使用CAS操作将自己的线程ID放到Mark Word中;
  • 若之后再获取锁,则测试一下当前的Mark Word中是否存着当前线程的ID;
  • 若测试成功,则成功获取到锁;
  • 若测试失败,则检查Mark Word中倒数第三位(即偏向锁的标识):若为0,则说明此时无锁,使用CAS获取锁即可;若为1,说明此时是偏向锁,则使用CAS尝试将Mark Word中存储的线程ID置为当前线程ID。

(2) 偏向锁的撤销:
当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

(3) 偏向锁状态下的64位Mark Word:
在这里插入图片描述
当epoch的值大于40时,升级为轻量级锁。

你可能感兴趣的:(synchronized的优化——自适应自旋、偏向锁)