在 jdk1.5 版本(包含)之前,锁的状态只有两种状态:无锁状态和重量级锁状态,只要有线程访问共享资源对象,则锁直接成为重量级锁,jdk1.6 版本后,对 synchronized 锁进行了优化,新加了"偏向锁"和"轻量级锁",用来减少上下文的切换以提高性能,所以锁就有了 4 种状态。
- 无锁
对于共享资源,不涉及多线程的竞争访问。在 DK1.6 之后偏向锁的默认开启的,但是有一个偏向延迟,需要在 VM 启动之后的多少秒之后才能开启,这个可以通过 VM 参数进行设置,同时是否开启偏向锁也可以通过 VM 参数设置。- 偏向锁
共享资源首次被访问时 ,JVM 会对该共享资源对象做一些设置,比如将对象头中是否偏向锁标志位置为 1,对象头中的线程 ID 设置为当前线程 ID(注意:这里是操作系统的线程 ID),后续当前线程再次访问这个共享资源时,会根据偏向锁标识跟线程 ID 进行比对是否相同,比对成功则直接获取到锁(锁偏向于这个线程),进入临界区域 (就是被锁保护,线程间只能串行访问的代码),这也是synchronized 锁的可重入功能。- 轻量级锁
当多个线程同时申请共享资源锁的访问时,这就产生了竞争,JVM 会先尝试使用轻量级锁,以CAS 方式 来获取锁(一般就是自旋加锁,不阻塞线程采用循环等待 的方式),成功则获取到锁,状态为轻量级锁,轻量级锁竞争失败(达到一定的自旋次数还未成功)则锁升级到重量级锁。- 重量级锁
如果共享资源锁已经被某个线程持有,此时是偏向锁状态,**未释放锁前,再有其他线程来竞争时,则会升级到重量级锁,把竞争线程挂起,**重量级锁由操作系统来实现,所以性能消耗相对较高。
synchronized 和 volatile 区别
synchronized关键字和volatile关键字是两个互补的而非对立的
volatile关键字是线程同步的轻量级 实现,所以volatile性能肯定比synchronized关键字要好 。但是volatile关键字只能用于变量 而synchronized关键字可以修饰方法以及代码块 。volatile关键字能保证数据的可见性,但不能保证数据的原子性 。synchronized关键字两者都能保证。volatile关键字主要用于解决变量在多个线程之间的可见性,同时防止指令重排序。而synchronized关键字解决的是多个线程之间访问资源的同步性。
底层原理
底层实现涉及对象头、监视器锁,每个 java 对象都有一个对象头,包含锁的信息,对象头通常分为两部分:
- Mark Word(标记字段):用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID 等。
- Klass Pointer(类型指针):指向对象的类元数据,用于确定对象是哪个类的实例。
- synchronized 的核心是基于 JVM 的内部数据结构 Monitor,每个对象都有一个与之关联的 Monitor,当线程获取对象的 synchronized 锁时,实际上是在获取该对象的 Monitor。
- 当线程执行到 synchronized 时,先检查锁状态:线程首先检查对象头中的锁状态标志,然后尝试获取锁,执行同步代码块,释放锁。
