Synchronized关键字的深入分析

一、引言

在多线程编程中,正确地管理并发是确保程序正确运行的关键。Java提供了多种同步工具,其中synchronized关键字是最基本且最常用的同步机制之一。本文旨在深入解析synchronized的实现原理,探讨其在不同应用场景中的使用,并通过示例让读者更好地理解其工作机制。

二、Synchronized基本概念

2.1 定义和作用

synchronized关键字可以用来修饰方法或者代码块。在方法或代码块被执行时,它能够保证同一时刻只有一个线程执行该段代码。这一特性使得synchronized成为实现临界区(Critical Section)和避免竞态条件(Race Condition)的简便方法。

2.2 使用方法

  • 同步实例方法:锁定当前实例对象
public synchronized void method() {// 同步代码
}
  • 同步静态方法:锁定当前类的Class对象。
public static synchronized void staticMethod() {// 同步代码
}
  • 同步代码块:指定一个特定对象作为锁。
public void method() {synchronized(this) {// 同步代码}
}

2.3 对比其他关键字

volatilefinal相比,synchronized不仅能保证可见性和顺序性,还能保证原子性。volatile仅保证变量的修改可见性和禁止指令重排序,而final关键字则用于声明常量。

三、Synchronized的内部机制

3.1 Java内存模型(JMM)

JMM处理了变量的可见性、原子性问题,为开发者屏蔽了不同CPU的复杂性。它确保一个线程对共享变量的修改,能够被其他线程看到,是通过内存屏障实现的。

3.2 锁的状态

在 JDK 6 中虚拟机团队对锁进行了重要改进,优化了其性能引入了 偏向锁、轻量级锁、适应性自旋、锁消除、锁粗化等实现,其中 锁消除和锁粗化本文不做详细讨论其余内容我们将对其进行逐一探究。

总体上来说锁状态升级流程如下:
在这里插入图片描述

  • 无锁状态
  • 偏向锁:假定锁不会存在竞争,避免了大多数情况下的同步。
  • 轻量级锁:当锁是偏向锁时,被另一个线程访问,会升级为轻量级锁。
  • 重量级锁:多线程竞争激烈时,轻量级锁会升级为重量级锁。

3.3 各种锁的获取流程

偏向锁
流程

当线程访问同步块并获取锁时处理流程如下:

  • 检查 mark word 的线程 id 。
  • 如果为空则设置 CAS 替换当前线程 id。如果替换成功则获取锁成功,如果失败则撤销偏向锁。
  • 如果不为空则检查 线程 id为是否为本线程。如果是则获取锁成功,如果失败则撤销偏向锁。

持有偏向锁的线程以后每次进入这个锁相关的同步块时,只需比对一下 mark word 的线程 id 是否为本线程,如果是则获取锁成功。

如果发生线程竞争发生 2、3 步失败的情况则需要撤销偏向锁。

偏向锁的撤销
  1. 偏向锁的撤销动作必须等待全局安全点
  2. 暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态
  3. 撤销偏向锁恢复到无锁(标志位为 01)或轻量级锁(标志位为 00)的状态
优点

只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。

缺点

如果存在竞争会带来额外的锁撤销操作。

轻量级锁
加锁

多个线程竞争偏向锁导致偏向锁升级为轻量级锁

  1. JVM 在当前线程的栈帧中创建 Lock Reocrd,并将对象头中的 Mark Word 复制到 Lock Reocrd 中。(Displaced Mark Word)
  2. 线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向 Lock Reocrd 的指针。如果成功则获得锁,如果失败则先检查对象的 Mark Word 是否指向当前线程的栈帧如果是则说明已经获取锁,否则说明其它线程竞争锁则膨胀为重量级锁。
解锁
  1. 使用 CAS 操作将 Mark Word 还原
  2. 如果第 1 步执行成功则释放完成
  3. 如果第 1 步执行失败则膨胀为重量级锁。
优点

其性能提升的依据是对于绝大部分的锁在整个生命周期内都是不会存在竞争。在多线程交替执行同步块的情况下,可以避免重量级锁引起的性能消耗。

缺点

在有多线程竞争的情况下轻量级锁增加了额外开销。

自旋锁

自旋是一种获取锁的机制并不是一个锁状态。在膨胀为重量级锁的过程中或重入时会多次尝试自旋获取锁以避免线程唤醒的开销,但是它会占用 CPU 的时间因此如果同步代码块执行时间很短自旋等待的效果就很好,反之则浪费了 CPU 资源。默认情况下自旋次数是 10 次用户可以使用参数 -XX : PreBlockSpin 来更改。那么如何优化来避免此情况发生呢?我们来看适应性自旋。

适应性自旋锁

JDK 6 引入了自适应自旋锁,意味着自旋的次数不在固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果对于某个锁很少自旋成功那么以后有可能省略掉自旋过程以避免资源浪费。有了自适应自旋随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虛拟机就会变得越来越“聪明”了。

优点

竞争的线程不会阻塞挂起,提高了程序响应速度。避免重量级锁引起的性能消耗。

缺点

如果线程始终无法获取锁,自旋消耗 CPU 最终会膨胀为重量级锁。

重量级锁

在重量级锁中没有竞争到锁的对象会 park 被挂起,退出同步块时 unpark 唤醒后续线程。唤醒操作涉及到操作系统调度会有额外的开销。

ObjectMonitor 中包含一个同步队列(由 _cxq_EntryList 组成)一个等待队列( _WaitSet )。

  • 被notify或 notifyAll 唤醒时根据 policy 策略选择加入的队列(policy 默认为 0)
  • 退出同步块时根据 QMode 策略来唤醒下一个线程(QMode 默认为 0)

这里稍微提及一下管程这个概念。synchronized 关键字及 waitnotifynotifyAll 这三个方法都是管程的组成部分。可以说管程就是一把解决并发问题的万能钥匙。有两大核心问题管程都是能够解决的:

  • 互斥:即同一时刻只允许一个线程访问共享资源;
  • 同步:即线程之间如何通信、协作。

synchronizedmonitor锁机制和 JDK 并发包中的 AQS 是很相似的,只不过 AQS 中是一个同步队列多个等待队列。熟悉 AQS 的同学可以拿来做个对比。

3.3 锁升级过程

锁的升级是自动的,以减少锁的开销。例如,从偏向锁到轻量级锁的升级发生在第一个获取偏向锁的线程之外的线程尝试获取这个锁时。

3.4 对象头和锁标记位

Java对象头包含了对象的运行时数据,例如哈希码、GC标记位、锁状态等。这些信息对于锁的管理至关重要。
在这里插入图片描述

附录:队列协作流程图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/651135.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vivado 使用“链路 (Links)”窗口查看和更改链路设置

使用“链路 (Links) ”窗口查看和更改链路设置 创建链路后 , 就会将其添加到“ Links ”视图 ( 请参阅下图 ) 中 , 该视图是更改链路设置和查看状态的主要方法 , 也是最佳方法。 “ Links ”窗口中的每一行都对应 1 …

AI 语音机器人系统怎么搭建

搭建AI语音机器人系统通常包括以下几个关键步骤: 确定需求和技术选型:首先要明确AI语音机器人需要实现的功能,选择合适的技术框架和工具,如自然语言处理工具、语音识别工具等。 搜集和准备数据:收集和整理与业务相关…

一个不太好用的弹出层jquery.colorbox第二次点击不出来的解决方法

使用jquery.colorbox的时候第一次正常显示,但第二次的时候不显示。 需要先移除,再重新点击即可,代码如下 $.colorbox.remove();$.colorbox({href: url,iframe: true,width: options.width || 800,height: options.height || 600 });

Windows常见问题(技巧)总结

目录 问题Windows中更改快捷方式图标windows 中网速很慢,如何解决?因为Authenticated Users从其父系继承承权限,你无法删除此对象.Windows下的照片软件在哪个文件夹下?如何批量更改文件名?Windows 电脑pagefile.sys是什么文件,可以删除吗?U盘中打开文件时提示&a…

Linux下载及安装OpenSSL

文章目录 前言一、OpenSSL下载二、OpenSSL安装1.上传下载好的安装包到服务器2.解压3.切换目录4.配置config5.编译6.安装7.备份旧版本OpenSSL7.创建软链接8.添加OpenSSL动态链接库9.更新库缓存10.查看OpenSSL版本验证安装是否成功 前言 一般系统会自带有OpenSSL,我们…

PyQt5中QTablewidget生成右键菜单

QTablewidget生成右键菜单,需要自定义一个QTablewidget类 import sys from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem, QMenu, QAction, QDialog from PyQt5.QtCore import Qt from PyQt5 import QtCoreclass CustomTableWidget(QTabl…

搭建产品帮助体系简单易用思路

在这个数字化时代,产品帮助体系的在企业中占据着很大的地位。对于用户来说,一个简单易用的产品帮助体系非常重要,可以指引他们快速找到产品的相关信息。那么,如何搭建一个简单易用的产品帮助体系呢?下面,我…

2024/4/23 C++day1

有以下定义,说明哪些量可以改变哪些不可以改变? const char *p; 指针可以改变 值不可以改变 const (char *) p; 语法错误 char *const p; 指针不可以改变 值可以改变 const char* const p; 指针和值…

使用Excel生成sql脚本(insert/update/delete)

目录 前言 一、Excel文件脚本变量 二、操作示例 前言 在系统使用初期,存在某种原因,需要对数据库数据进行批量处理操作。往往都是通过制定Excel表格,通过Excel导入到数据库中,所以就弄一个excel生成sql的导入脚本,希…

中国茶叶在世界范围的普及

正如世界上所有美好的事物一样,茶的传播一样遭遇了反对的声音。 如反对者亨利萨威尔(1678)斥责饮茶是肮脏的习俗。 乔纳斯汉威在《论茶》(1756)中说,男人饮茶会丧失身材威仪,女人饮茶则容颜尽…

玩转nginx的配置文件3

1. limit_req_zone配置限流 limit_req_zone $binary_remote_addr zonemylimit:10m rate10r/s;upstream myweb {server 10.0.105.196:80 weight1 max_fails1 fail_timeout1;}server {listen 80;server_name localhost;location /login {limit_req zonemylimit;proxy_pass http:…

vue项目使用百度地图

打开百度地图开放平台 百度地图开放平台 | 百度地图API SDK | 地图开发 在控制台新建应用 复制访问应用的ak 可修改地图样式 使用部分 <!-- 引入地图 --><div class"main-aside"><div id"b-map-container"></div></div> …