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

相关文章
|
3月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
103 0
|
4月前
|
存储 缓存 Java
我们来详细讲一讲 Java NIO 底层原理
我是小假 期待与你的下一次相遇 ~
160 2
|
3月前
|
缓存 Java API
Java 面试实操指南与最新技术结合的实战攻略
本指南涵盖Java 17+新特性、Spring Boot 3微服务、响应式编程、容器化部署与数据缓存实操,结合代码案例解析高频面试技术点,助你掌握最新Java技术栈,提升实战能力,轻松应对Java中高级岗位面试。
343 0
|
3月前
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
104 24
|
3月前
|
存储 缓存 安全
深入讲解 Java 并发编程核心原理与应用案例
本教程全面讲解Java并发编程,涵盖并发基础、线程安全、同步机制、并发工具类、线程池及实际应用案例,助你掌握多线程开发核心技术,提升程序性能与响应能力。
145 0
|
安全 Java 程序员
Java并发编程中的锁机制与优化策略
【6月更文挑战第17天】在Java并发编程的世界中,锁是维护数据一致性和线程安全的关键。本文将深入探讨Java中的锁机制,包括内置锁、显式锁以及读写锁的原理和使用场景。我们将通过实际案例分析锁的优化策略,如减少锁粒度、使用并发容器以及避免死锁的技巧,旨在帮助开发者提升多线程程序的性能和可靠性。
|
安全 Java 编译器
Java并发编程中的锁优化策略
【5月更文挑战第30天】 在多线程环境下,确保数据的一致性和程序的正确性是至关重要的。Java提供了多种锁机制来管理并发,但不当使用可能导致性能瓶颈或死锁。本文将深入探讨Java中锁的优化策略,包括锁粗化、锁消除、锁降级以及读写锁的使用,以提升并发程序的性能和响应能力。通过实例分析,我们将了解如何在不同场景下选择和应用这些策略,从而在保证线程安全的同时,最小化锁带来的开销。
|
安全 Java 开发者
Java并发编程中的锁优化策略
【5月更文挑战第30天】 在Java并发编程领域,锁机制是实现线程同步的关键手段之一。随着JDK版本的发展,Java虚拟机(JVM)为提高性能和降低延迟,引入了多种锁优化技术。本文将深入探讨Java锁的优化策略,包括偏向锁、轻量级锁以及自旋锁等,旨在帮助开发者更好地理解和应用这些高级特性以提升应用程序的性能。
|
安全 Java
Java并发编程中的锁优化策略
【5月更文挑战第25天】在Java并发编程中,锁是实现线程同步的关键。然而,锁的使用可能导致性能下降,尤其是在高并发场景下。为了提高程序的执行效率,本文将探讨几种常用的锁优化策略,包括自旋锁、适应性锁和锁粗化等技术。通过这些优化策略,我们可以在保证线程安全的同时,提高程序的运行效率。
62 3

热门文章

最新文章