Synchronized锁升级之路:从无锁到重量级锁的演变

开发 前端
synchronized的锁升级过程是从无锁状态开始,根据线程对锁的争用情况逐步升级到偏向锁、轻量级锁和重量级锁的过程。

今天我们来和大家一起来一下关于这个锁的问题,为什么锁一直比较收到关注呢?因为在 Java 的锁机制能够保证安全,这时候有些朋友就会说,说线程安全,那么效率势必低下,很多时候就压根不需要使用,这说的也确实是对的,因为一般很多开发在日常工作中,很少会使用到,但是呢,在面试的过程中,会经常性的问题,今天我们就来聊聊一个老生常谈的一个面试题,Synchronized 的锁的升级过程。

为什么需要锁

Java中的锁是一种同步机制,可以确保多个线程之间共享资源的互斥访问,从而避免出现数据竞争和线程安全问题。使用锁的主要目的是保证代码的正确性和可靠性。

Java中的锁能够解决以下实际问题:

  • 数据竞争:在多线程环境中,如果多个线程同时访问共享数据,就会产生数据竞争问题。使用锁可以确保同一时间只有一个线程可以访问共享资源,避免数据竞争和数据不一致的问题。
  • 线程安全:Java中的锁可以确保线程安全,避免多个线程之间的干扰和竞争,从而保证代码的正确性和可靠性。
  • 性能优化:Java中的锁可以用于优化程序的性能,比如使用读写锁来实现对数据的读写分离,从而提高程序的并发性能。
  • 死锁问题:Java中的锁可以用于避免死锁问题,比如使用一致性的加锁顺序,避免出现循环依赖的情况。

总之,Java中的锁机制是保证多线程并发安全的重要手段,可以用于解决数据竞争、线程安全、性能优化和死锁问题等实际问题。

Synchronized

synchronized是Java中的一个关键字,它提供了一种内置锁机制,用于确保多个线程在访问共享资源时的同步性。

使用方式

  • 修饰方法:直接在方法声明上加上synchronized关键字,表示整个方法是同步的。此时,锁是当前实例对象(对于非静态方法)或Class对象(对于静态方法)。
  • 修饰代码块:使用synchronized(object)来指定一个对象作为锁,只有持有该对象锁的线程才能进入synchronized块。这种方式可以更加灵活地控制需要同步的代码范围。

原理与特性

  • 互斥性:当一个线程进入由synchronized修饰的代码块或方法时,它会获取对象的锁,其他试图进入该代码块或方法的线程将被阻塞,直到锁被释放。这确保了同一时间只有一个线程可以执行synchronized保护的代码段。
  • 可重入性:对于同一个线程来说,synchronized块是可重入的,即一个线程可以多次获取同一个对象的锁。
  • 可见性:synchronized保证了内存可见性,即当一个线程修改了共享变量的值后,其他线程能够立即看到这个修改。这是通过JVM的内存屏障指令实现的,确保了在获取锁之前和释放锁之后,相关的内存操作会被刷新到主内存或从主内存重新读取。

Synchronized 锁的升级过程

在Java中,synchronized关键字的锁升级过程是一个动态的过程,旨在提高并发性能并减少线程之间的争用。这个过程从最初的无锁状态开始,根据线程对锁的争用情况,逐步升级到更高级别的锁状态。

我们来看一下他的升级过程:

无锁状态

对象刚被创建时,没有线程对其加锁,此时处于无锁状态。

偏向锁

  • 当第一个线程访问某个对象并尝试获取锁时,JVM会利用CAS(Compare-And-Swap)操作在对象的对象头(Mark Word)中记录下当前线程的ID和偏向锁标记位(通常设置为1)。
  • 如果下一次还是这个线程访问该对象,则只需要检查对象头中的线程ID是否与自己的ID相同,如果相同则直接获得锁,无需再进行CAS操作。这种情况下,锁就保持在偏向锁状态,整个过程几乎没有任何性能开销。
  • 如果在持有偏向锁期间,其他线程尝试访问该对象并获取锁,偏向锁会被撤销,并尝试升级为轻量级锁。

轻量级锁

  • 当偏向锁被撤销后,锁会升级到轻量级锁状态。
  • 在轻量级锁状态下,JVM会在当前线程的栈帧中创建一个锁记录(Lock Record),并将对象头中的Mark Word复制到该锁记录中,同时对象头中会有一个指针指向这个锁记录。
  • 当前线程会进入自旋(Spinning)状态,即不断尝试重新获取锁,而不是立即阻塞。自旋的目的是为了避免线程切换带来的性能开销,因为线程切换涉及到操作系统层面的操作,开销相对较大。
  • 如果自旋过程中成功获取到锁,则继续执行后续代码;如果自旋超过一定次数(通常是10次)仍未获取到锁,或者有其他线程参与锁竞争,则轻量级锁会膨胀为重量级锁。

重量级锁

  • 当轻量级锁无法满足并发需求时,锁会升级为重量级锁。
  • 在重量级锁状态下,如果当前线程未获取到锁,则会进入阻塞状态,等待其他线程释放锁。当锁被释放后,阻塞的线程会被唤醒并重新尝试获取锁。
  • 重量级锁的实现依赖于操作系统的互斥量(Mutex)或其他同步机制,因此涉及到用户态和内核态的切换,开销相对较大。

总结

  • synchronized的锁升级过程是从无锁状态开始,根据线程对锁的争用情况逐步升级到偏向锁、轻量级锁和重量级锁的过程。
  • 偏向锁和轻量级锁是JVM为了提高并发性能而引入的优化措施,它们可以减少线程切换带来的性能开销。
  • 重量级锁是当轻量级锁无法满足并发需求时的最终选择,它依赖于操作系统的同步机制来实现。
责任编辑:武晓燕 来源: 一安未来
相关推荐

2024-03-18 12:21:28

Java轻量级锁重量级锁

2024-01-11 08:12:20

重量级监视器

2023-10-10 07:55:41

JDK8轻量级锁

2022-10-21 16:39:56

JDK优化

2024-09-26 08:22:03

2022-03-08 08:44:13

偏向锁Java内置锁

2019-10-08 14:40:53

Java线程

2023-11-08 08:18:19

锁升级多线程

2024-06-27 08:55:41

2024-02-26 07:36:09

lockJava语言

2019-11-28 16:00:06

重入锁读写锁乐观锁

2024-03-06 08:00:56

javaAQS原生

2024-01-29 01:08:01

悲观锁递归锁读写锁

2021-03-31 10:05:26

偏向锁轻量级锁

2022-07-04 08:01:01

锁优化Java虚拟机

2019-01-04 11:18:35

独享锁共享锁非公平锁

2021-11-26 06:43:19

Java分布式

2018-07-31 10:10:06

MySQLInnoDB死锁

2011-11-28 12:31:20

JavaJVM

2021-08-03 07:40:46

Synchronize锁膨胀性能
点赞
收藏

51CTO技术栈公众号