Java面试题之synchronized关键字原理以及锁相关

简介: 录一、Java中锁的概念二、同步关键字synchronized特性1、锁消除示例2、锁粗化示例三、synchronized关键字原理1、关于Mark Word2、锁的状态变化(1) 无锁 → 轻量级锁(2) 轻量级锁 → 重量级锁(3) 关于偏向锁(加锁之后不解锁,针对单线程)(4) 完整的锁升级过程

目录

一、Java中锁的概念

二、同步关键字synchronized特性

1、锁消除示例

2、锁粗化示例

三、synchronized关键字原理

1、关于Mark Word

2、锁的状态变化

(1) 无锁 → 轻量级锁

(2) 轻量级锁 → 重量级锁

(3) 关于偏向锁(加锁之后不解锁,针对单线程)

(4) 完整的锁升级过程

一、Java中锁的概念



自旋锁:是指当一个线程获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能被成功获取,直到获取到锁才会退出循环。

乐观锁:假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,重试修改。

悲观锁:假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。

独享锁(写):给资源加上写锁,线程可以修改资源,其它线程不能再加锁(单写)。

共享锁(读):给资源加上读锁后只能读不能修改,其它线程也只能加读锁,不能加写锁(多度)。看成Semaphore(信号量)理解即可。

可重入锁&不可重入锁:线程拿到一把锁之后,可以自由进入同一把锁所同步的其它代码。

公平锁&非公平锁:争抢锁的顺序,如果是按先来后到,则为公平。即能保证抢锁的顺序和抢到锁的顺序一致则为公平锁。

二、同步关键字synchronized特性


锁相关的优化

  • 锁消除 :开启锁消除的参数有 -XX:+DoEscapeAnalysis-XX:+EliminateLocks
  • 锁粗化:JDK做了锁粗化的优化,但我们自己可从代码层面优化。

1、锁消除示例

/**
 * 锁消除示例,JIT即时编译,进行了锁消除
 * @author 刘亚楼
 * @date 2020/1/16
 */
public class LockEliminationExample {
  /**
   * StringBuilder线程不安全,StringBuffer用了synchronized关键字,是线程安全的
   * 针对下面这种单线程加锁、解锁操作,JIT会进行优化,进行锁消除
   */
  public static void eliminateLock() {
    StringBuffer stringBuffer = new StringBuffer();
    stringBuffer.append("a");
    stringBuffer.append("b");
    stringBuffer.append("c");
    stringBuffer.append("a");
    stringBuffer.append("b");
    stringBuffer.append("c");
    stringBuffer.append("a");
    stringBuffer.append("b");
    stringBuffer.append("c");
  }
}

2、锁粗化示例


/**
 * 锁粗化示例
 * @author 刘亚楼
 * @date 2020/1/16
 */
public class LockCoarseningExample {
  /**
   * 针对下面这种无意义的加锁操作,JIT会进行优化,对变量i的所有操作放到一个同步代码块里
   */
  public static void lockCoarsening() {
    int i = 0;
    synchronized (LockCoarseningExample.class) {
      i++;
    }
    synchronized (LockCoarseningExample.class) {
      i--;
    }
    synchronized (LockCoarseningExample.class) {
      i++;
    }
    synchronized (LockCoarseningExample.class) {
      i++;
      i--;
      i++;
    }
  }
}


备注:锁消除锁粗化的区别在于锁消除是针对单个线程重复加解锁做的优化,最终没有锁的存在。而锁粗化不只是针对单线程,且最终还是有锁的存在。

三、synchronized关键字原理



1、关于Mark Word

首先,对象在堆中由对象头、实例数据和对齐填充组成。


对象头包含两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向锁id等,这部分数据官方称为"Mark Word"。


对象头的另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。


synchronized实现的锁是通过改变对象头的"Mark Word"来实现的。


"Mard Word"在32位和64位的虚拟机(未开启压缩指针)中分别为32位和64位。32位虚拟机"Mark Word"如下:

d5ba82b0479647ec98f3e9b660f84b54.png、锁的状态变化

(1) 无锁 → 轻量级锁

无锁变成轻量级锁时,多个线程会读取对象的对象头的无锁状态mark word内容,然后进行cas操作进行修改,预期值是无锁状态mark word内容,新值是轻量级锁状态mark word内容,若修改成功,Lock record address指向成功获取锁的线程的Lock Record。


演示流程如下:

c2559b55eb97421f8b6aa3b5cc7caf07.png(2) 轻量级锁 → 重量级锁

由于未成功获取锁的线程会自旋,长时间自旋会消耗CPU资源,因此自旋到一定次数会进行锁升级,由轻量级锁转变为重量级锁。


重量级锁是通过object monitor(对象监视器)实现的,对象监视器包括entryList(锁池)、owner(持锁者)、waitSet(等待集合)等。


升级为重量级锁时对象头mark word的内容是monitor address(对象监视器地址),指向对象监视器。6e1659b4bf9e4114ab5e7ca5d9e84b16.png

备注:抢锁失败线程会进入entryList(锁池),在调用wait方法后,线程会进入waitSet(等待集合),waitSet中的线程被唤醒后会重新进入entryList。

(3) 关于偏向锁(加锁之后不解锁,针对单线程)

所谓偏向就是偏心,单线程加锁之后就不再解锁,减少了加锁→业务处理→释放锁→加锁操作流程。


在JDK6以后,默认已经开启了偏向锁这个优化,通过JVM参数-XX:-UseBiasedLocking来禁用偏向锁,若偏向锁开启,只有一个线程抢锁,可获取到偏向锁。


关于偏向锁Mark Word内容如下:

546604f73ae54cb2865de18ba723b331.png

546604f73ae54cb2865de18ba723b331.png偏向标记第一次有用,出现过争用后就没用了。


偏向锁本质就是无锁,如果没有发生过任何多线程争抢锁的情况,JVM认为就是单线程,无需做同步。


备注:JVM为了少干活,同步在JVM底层是有很多操作来实现的,如果没有争用,就不需要去做同步操作。

(4) 完整的锁升级过程

如果未开启偏向锁,无锁状态会先升级为轻量级锁,轻量级锁自选到一定程度升级为重量级锁。


如果开启了偏向锁,有两种情况:


当锁未被占用时,会升级为无锁,无锁再升级为轻量级锁,再由轻量级锁升级为重量级锁。

当锁被占用时,会升级为轻量级锁,再由轻量级锁升级到重量级锁。

546604f73ae54cb2865de18ba723b331.png

相关文章
|
9天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
9天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
9天前
|
缓存 安全 Java
Java volatile关键字:你真的懂了吗?
`volatile` 是 Java 中的轻量级同步机制,主要用于保证多线程环境下共享变量的可见性和防止指令重排。它确保一个线程对 `volatile` 变量的修改能立即被其他线程看到,但不能保证原子性。典型应用场景包括状态标记、双重检查锁定和安全发布对象等。`volatile` 适用于布尔型、字节型等简单类型及引用类型,不适用于 `long` 和 `double` 类型。与 `synchronized` 不同,`volatile` 不提供互斥性,因此在需要互斥的场景下不能替代 `synchronized`。
2090 3
|
9天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
31 3
|
2月前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
82 14
|
2月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
28天前
|
Java 程序员
面试官的加分题:super关键字全解析,轻松应对!
小米,29岁程序员,通过一个关于Animal和Dog类的故事,详细解析了Java中super关键字的多种用法,包括调用父类构造方法、访问父类成员变量及调用父类方法,帮助读者更好地理解和应用super,应对面试挑战。
40 3
|
2月前
|
Java 编译器 程序员
Java面试高频题:用最优解法算出2乘以8!
本文探讨了面试中一个看似简单的数学问题——如何高效计算2×8。从直接使用乘法、位运算优化、编译器优化、加法实现到大整数场景下的处理,全面解析了不同方法的原理和适用场景,帮助读者深入理解计算效率优化的重要性。
36 6
|
2月前
|
JavaScript 前端开发 Java
java中的this关键字
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。自学前端2年半,正向全栈进发。若我的文章对你有帮助,欢迎关注,持续更新中!🎉🎉🎉
53 9
|
2月前
|
设计模式 JavaScript 前端开发
java中的static关键字
欢迎来到瑞雨溪的博客,博主是一名热爱JavaScript和Vue的大一学生,致力于全栈开发。如果你从我的文章中受益,欢迎关注我,将持续分享更多优质内容。你的支持是我前进的动力!🎉🎉🎉
54 8

热门文章

最新文章