多线程安全——synchronized锁升级

 锁的状态

在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,需要调用操作系统相关接口。而操作系统实现线程之间的切换时需要从用户态转换到内核态,有可能给线程加锁消耗的时间比有用操作消耗的时间更多。

Java1.6后对synchronized 进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的 Java1.7 与 1.8 中,均对该关键字的实现机理做了优化。引入了偏向锁和轻量级锁。都是在对象头中有标记位,不需要经过操作系统加锁。

优点

缺点

适用场景

偏向锁

加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。

如果线程间存在锁竞争,会带来额外的锁撤销的消耗。

适用于只有一个线程访问同步块场景。

轻量级锁

竞争的线程不会阻塞,提高了程序的响应速度。

如果始终得不到锁竞争的线程使用自旋会消耗CPU。

追求响应时间。

同步块执行速度非常快。

重量级锁

线程竞争不使用自旋,不会消耗CPU。

线程阻塞,响应时间缓慢。

追求吞吐量。

同步块执行速度较长。

多线程安全——synchronized锁升级_第1张图片

无锁

无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。

一个锁对象刚刚开始创建的时候,没有任何线程来访问它,它是可偏向的,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问他的时候,它会偏向这个线程。此时锁状态为无锁状态,锁标志位为 01。

偏向锁

偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。

在大多数情况下,锁总是由同一线程多次获得,不存在多线程竞争,所以出现了偏向锁其目标就是在只有一个线程执行同步代码块时能够提高性能。

偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。

偏向锁在JDK 6及以后的JVM里是默认启用的。可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,关闭之后程序默认会进入轻量级锁状态。

无锁和偏向锁的锁标志位都是01,只是在前面的1bit区分了这是无锁状态还是偏向锁状态

偏向锁加锁过程

1、首先获取锁对象的 Markword,判断是否处于可偏向状态。

  • (biased_lock=1、且 ThreadId 为空),确认为可偏向状态。

2、如果是可偏向状态,则通过CAS 操作,把当前线程的ID写入到 MarkWord

  • 如果 CAS 成功,表示已经获得了锁对象的偏向锁,接着执行同步代码块
  • 如果 CAS 失败,说明有其他线程已经获得了偏向锁,这种情况说明当前锁存在竞争,需要撤销已获得偏向锁的线程,并且把它持有的锁升级为轻量级锁(这个操作需要等到全局安全点,也就是没有线程在执行字节码)才能执行

3、如果是已偏向状态,需要检查 markword 中存储的ThreadID 是否等于当前线程的 ThreadID

  • 如果相等,不需要再次获得锁,可直接执行同步代码块
  • 如果不相等,则表示有竞争。当到达全局安全点(SafePoint)时,会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否存活(因为可能持有偏向锁的线程已经执行完毕,但是该线程并不会主动去释放偏向锁),如果线程不处于活动状态,则将对象头置为无锁状态(标志位为01),然后重新偏向新的线程;如果线程仍然活着,撤销偏向锁后升级到轻量级锁的状态(标志位为00),此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁

4、执行同步代码

偏向锁释放过程

偏向锁在遇到其他线程竞争锁时,持有偏向锁的线程才会释放锁(保证一个线程重入不需要每次都CAS置换相同的线程id),线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁是否处于被锁定状态,撤销偏向锁后恢复到无锁(标志位为01,线程不会主动释放锁,可能到安全点时,线程已结束)或轻量级锁(标志位为00)的状态

多线程安全——synchronized锁升级_第2张图片

轻量级锁

是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。

“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是, 轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁

轻量级锁加锁过程

1、在代码进入同步块的时候,如果对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝,然后拷贝对象头中的 Mark Word 复制到锁记录中(官方称之为 Displaced Mark Word)

多线程安全——synchronized锁升级_第3张图片

2、拷贝成功后,虚拟机将使用 CAS 操作尝试将锁对象的 Mark Word 更新为指向 Lock Record 的指针,并将 Lock Record里的 owner 指针指向锁对象的 Mark Word。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为 00 ,表示此对象处于轻量级锁定状态。

多线程安全——synchronized锁升级_第4张图片

 3、如果这个更新操作失败了,虚拟机首先会检查锁对象的 Mark Word 是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,自适应自旋,当自旋超过指定次数时仍然无法获得锁,此时锁会膨胀升级为重量级锁,锁标志的状态值变为 10 ,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

轻量级锁解锁过程

1、通过CAS操作将Displaced Mark Word替换回到对象头,

2、如果成功,则表示没有竞争发生,整个同步过程完成。

3、如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁,为避免无用的自旋,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。

多线程安全——synchronized锁升级_第5张图片

重量级锁

轻量级锁所适应的场景是线程近乎交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。

重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现。

这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为 “重量级锁”

锁升级

随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的, 也就是说只能从低到高升级,不会出现锁的降级)

多线程安全——synchronized锁升级_第6张图片

多线程安全——synchronized锁升级_第7张图片

偏向所锁,轻量级锁都是乐观锁,重量级锁是悲观锁

  • 一个对象刚开始实例化的时候,没有任何线程来访问它的时候。它是可偏向的,意味着,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程,此时,对象持有偏向锁。偏向第一个线程,这个线程在修改对象头成为偏向锁的时候使用CAS操作,并将对象头中的ThreadID改成自己的ID,之后再次访问这个对象时,只需要对比ID,不需要再使用CAS在进行操作。
  • 一旦有第二个线程访问这个对象,因为偏向锁不会主动释放,所以第二个线程可以看到对象时偏向状态,这时表明在这个对象上已经存在竞争了。检查原来持有该对象锁的线程是否依然存活,如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程。如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级为轻量级锁,(偏向锁就是这个时候升级为轻量级锁的),此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁;如果不存在使用了,则可以将对象回复成无锁状态,然后重新偏向。
  • 轻量级锁认为竞争存在,但是竞争的程度很轻,一般两个线程对于同一个锁的操作都会错开,或者说稍微等待一下(自旋),另一个线程就会释放锁。 但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁膨胀为重量级锁,重量级锁使除了拥有锁的线程以外的线程都阻塞,防止CPU空转。

文献

Synchronized的原理及其实现

Java synchronized原理总结

你可能感兴趣的:(Java,多线程与高并发,Java,多线程,thread,线程安全,synchronized)