synchronized的底层实现

一、概念

(一)作用

  • 确保线程互斥的访问同步代码
  • 保证共享变量的修改能够及时可见
  • 有效解决重排序问题

(二)使用场景

  • 修饰代码块
  • 修饰方法(普通方法和静态方法)

(三)可重入锁和不可重入锁

1.不可重入锁

当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞。

不可重入锁实现:
synchronized的底层实现_第1张图片
2.可重入锁

如果某个线程试图获取一个已经由他自己持有的锁,这个请求可以成功,那么此时的锁就是可重入锁

可重入锁实现:
synchronized的底层实现_第2张图片

为每个锁关联一个持有者持有计数值,当计数值为 0 时,这个锁会被认为没有被任何线程持有,当现场请求一个未被持有的锁时,jvm 会把这个锁给这个线程,并记下这个锁的持有者,同时计数值置为 1,如果同一个线程再次获取这个锁,计数值将递增,当线程退出同步代码块时,计数值将递减,当计数值为 0 时,锁将被释放。


二、实现原理

synchronized 是由一对 monitorenter/monitorexit 指令实现的,monitor 对象是同步的基本实现单元。在 JVM 处理字节码会出现相关指令。

jvm 基于进入和退出 Monitor 对象来实现方法同步和代码块同步。

代码块的同步是利用 monitorenter 和 monitorexit 这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当 jvm 执行到 monitorenter 指令时,当前线程试图获取 monitor 对象的所有权,如果未加锁或者已经被当前线程所持有,就把锁的计数器 + 1;当执行 monitorexit 指令时,锁计数器 - 1;当锁计数器为 0 时,该锁就被释放了。如果获取 monitor 对象失败,该线程则会进入阻塞状态,直到其他线程释放锁。

方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM 可以从方法常量池中的方法表结构 (method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有 monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成 (无论是正常完成还是非正常完成) 时释放 monitor。


三、锁升级原理(锁膨胀)

Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么 Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为 “重量级锁”。

Java SE 1.6 为了减少获得锁和释放锁带来的性能消耗,引入了 “偏向锁” 和 “轻量级锁”:锁一共有 4 种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级。

  • 无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。

  • 偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带来的性能开销。

  • 轻量级锁:轻量级锁是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁,线程 B 会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。

  • 重量级锁:又有第三个线程来访时,轻量级锁也会升级为重量级锁。当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。


【Java 面试那点事】

这里致力于分享 Java 面试路上的各种知识,无论是技术还是经验,你需要的这里都有!

这里可以让你【快速了解 Java 相关知识】,并且【短时间在面试方面有跨越式提升

面试路上,你不孤单!
在这里插入图片描述

你可能感兴趣的:(Java面试知识汇总,java,面试)