Java关键字synchronized原理与锁的状态详解

 更新时间:2022年08月10日 10:37:53   作者:凌波漫步&  
在Java当中synchronized关键字通常是用来标记一个方法或者代码块。本文将通过示例为大家详细介绍一下Synchronized的各种使用方法,需要的可以参考一下

一、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"如下:

2、锁的状态变化

(1) 无锁 → 轻量级锁

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

演示流程如下:

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

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

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

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

演示流程如下:

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

(3) 关于偏向锁

加锁之后不解锁,针对单线程

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

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

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

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

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

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

(4) 完整的锁升级过程

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

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

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

到此这篇关于Java关键字synchronized原理与锁的状态详解的文章就介绍到这了,更多相关Java synchronized内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

    SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

    本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLocal保存请求中携带的用户信息,ThreadLocal通过为每个线程维护独立的变量副本,解决了线程安全问题,感兴趣的朋友一起看看吧
    2025-02-02
  • springboot使用EMQX(MQTT协议)的实现

    springboot使用EMQX(MQTT协议)的实现

    最近由于iot越来越火, 物联网的需求越来越多, 那么理所当然的使用mqtt的场景也就越来越多,本文主要介绍了springboot使用EMQX(MQTT协议)的实现,感兴趣的可以了解一下
    2023-10-10
  • Java实现邮件发送功能

    Java实现邮件发送功能

    这篇文章主要为大家详细介绍了Java实现邮件发送功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • SpringBoot3整合SpringDoc OpenAPI生成接口文档的详细过程

    SpringBoot3整合SpringDoc OpenAPI生成接口文档的详细过程

    SpringDoc OpenAPI 是一个强大的工具,能够帮助我们轻松生成 OpenAPI 3.0 规范的文档,并提供交互式的 Swagger UI 界面,所以本文给大家介绍了SpringBoot3整合SpringDoc OpenAPI生成接口文档的详细过程,需要的朋友可以参考下
    2024-07-07
  • QTabWidget标签实现双击关闭的方法(推荐)

    QTabWidget标签实现双击关闭的方法(推荐)

    这篇文章主要介绍了QTabWidget标签实现双击关闭的方法(推荐)的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2016-06-06
  • ZooKeeper集群操作及集群Master选举搭建启动

    ZooKeeper集群操作及集群Master选举搭建启动

    这篇文章主要为大家介绍了ZooKeeper集群操作及集群Master选举搭的建启动详解,<BR>有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • SpringBoot中动态数据源配置与使用详解

    SpringBoot中动态数据源配置与使用详解

    在现代应用中,处理多数据源是常见的需求,在 Spring Boot 中,这样的需求可以通过动态数据源来轻松实现,本篇博客将详细介绍如何在 Spring Boot 中配置和使用动态数据源,并演示如何切换到指定的数据源,需要的朋友可以参考下
    2024-10-10
  • JavaWeb 入门:Hello Servlet

    JavaWeb 入门:Hello Servlet

    这篇文章主要介绍了Servlet开发JavaWeb工程示例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-07-07
  • zookeeper实战之实现分布式锁的方法

    zookeeper实战之实现分布式锁的方法

    Zookeeper实现分布式锁比Redis简单,Zookeeper有一个特性,多个线程在Zookeeper里创建同一个节点时,只有一个线程执行成功,Zookeeper主要是利用临时有序节点这一特性实现分布式锁,感兴趣的朋友跟随小编一起学习吧
    2022-11-11
  • SpringBoot+SpringSecurity实现认证的流程详解

    SpringBoot+SpringSecurity实现认证的流程详解

    这篇文章主要介绍了SpringBoot+SpringSecurity实现认证的流程,文中通过代码示例和图文结合的方式讲解的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2024-05-05

最新评论