JAVA:深入了解Java中的Synchronized关键字

1、简述

在Java中,多线程编程是一项常见的任务,然而,它也伴随着一系列潜在的问题,比如竞态条件(Race Condition)和数据不一致性。为了解决这些问题,Java提供了一种同步机制,即synchronized关键字。本文将深入探讨Java中synchronized技术,介绍它的基本概念、用法和一些最佳实践。

2、关键特性

synchronized关键字可以保证并发编程的三大特性:原子性、可见性、有序性,而volatile关键字只能保证可见性和有序性,不能保证原子性,也称为是轻量级的synchronized。

  • 原子性:一个或多个操作全部执行成功或者全部执行失败。synchronized关键字可以保证只有一个线程拿到锁,访问共享资源。
  • 可见性:当一个线程对共享变量进行修改后,其他线程可以立刻看到。 执行synchronized时,会对应执行lock、unlock原子操作,保证可见性。
  • 有序性:程序的执行顺序会按照代码的先后顺序执行。

synchronized关键字可以实现什么类型的锁?

  • 悲观锁:synchronized关键字实现的是悲观锁,每次访问共享资源时都会上锁。

  • 非公平锁:synchronized关键字实现的是非公平锁,即线程获取锁的顺序并不一定是按照线程阻塞的顺序。

  • 可重入锁:synchronized关键字实现的是可重入锁,即已经获取锁的线程可以再次获取锁。

  • 独占锁或者排他锁:synchronized关键字实现的是独占锁,即该锁只能被一个线程所持有,其他线程均被阻塞。

3、基本使用

3.1 synchronized关键字的基本概念

synchronized是Java中用于实现同步的关键字,它可以用来修饰方法或代码块。当一个线程进入一个由synchronized修饰的方法或代码块时,它将自动获得锁,其他线程必须等待直到该线程释放锁。这确保了在同一时刻只有一个线程可以执行被synchronized修饰的代码。

public synchronized void synchronizedMethod() {// 同步的代码块
}
3.2 对象级别的锁和类级别的锁

在synchronized中,锁可以是对象级别的,也可以是类级别的。对象级别的锁是基于对象实例的,而类级别的锁是基于类的Class对象的。

// 对象级别的锁
public synchronized void objectLevelLock() {// 同步的代码块
}// 类级别的锁
public static synchronized void classLevelLock() {// 同步的代码块
}
3.3 同步代码块

除了修饰方法,synchronized还可以用于同步代码块。这使得我们可以更加精细地控制同步的范围,提高程序的性能。

public void someMethod() {// 非同步的代码synchronized (lockObject) {// 需要同步的代码块}// 非同步的代码
}

4、实现原理

Synchronized的底层实现原理是完全依赖JVM虚拟机的,所以谈synchronized的底层实现,就不得不谈数据在JVM内存的存储:Java对象头,以及Monitor对象监视器。
在这里插入图片描述
以下我们来看一下Monitor的实现原理:

ObjectMonitor() {_header       = NULL;_count        = 0;  //锁的计数器,获取锁时count数值加1,释放锁时count值减1_waiters      = 0,  //等待线程数_recursions   = 0;  // 线程重入次数_object       = NULL;  // 存储Monitor对象_owner        = NULL;  // 持有当前线程的owner_WaitSet      = NULL;  // wait状态的线程列表_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ;  // 阻塞在EntryList上的单向线程列表FreeNext      = NULL ;_EntryList    = NULL ;  // 处于等待锁状态block状态的线程列表_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;_previous_owner_tid = 0;}
4.1 运行流程

通过以下代码添加同步锁来解析当前线程运行流程:

public  void synch1(){synchronized(this){try {synchronized (this){TimeUnit.MINUTES.sleep(2);}System.out.println(Thread.currentThread().getName()+" is runing");} catch (InterruptedException e) {e.printStackTrace();}}
}

通过JAR 自带的jconsole.exe 我们可以很清楚的看到当前运行的6个线程状况:
在这里插入图片描述
也可以通过Java自带的Jstack + PID 来查看线程运行情况:

jstack 21836

在这里插入图片描述

4.2 如何加锁

首先我们来看看对方法同步的运行情况,可以通过反编译class类来查看当前Java代码运行的情况:

public synchronized static void synch0(){try {TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName()+" is runing");} catch (InterruptedException e) {e.printStackTrace();}
}

通过Javap指令来解析当前class类:

javap -v SynchronizedDemo

找到反编译指定的代码块,我们可以很清楚的看到同步的方法,是通过flags的ACC_SYNCHRONIZED来实现加锁和解锁的:
在这里插入图片描述

接下来我们对代码块实现加Synchronized操作:

public  void synch1(){synchronized(this){try {synchronized (this){TimeUnit.MINUTES.sleep(2);}System.out.println(Thread.currentThread().getName()+" is runing");} catch (InterruptedException e) {e.printStackTrace();}}
}

同理反编译,我们可以很清楚的看到同步代码块是通过Monitorenter和Monitorexit来实现同步加锁的:
在这里插入图片描述
小结:Java虚拟机是通过进入和退出Monitor对象来实现代码块同步和方法同步的,代码块同步使用的是monitorenter和 monitorexit 指令实现的,而方法同步是通过Access flags后面的标识来确定该方法是否为同步方法

5. 锁优化

在Java多线程编程中,synchronized关键字是一种常见的同步机制,用于保护共享资源,防止多个线程同时访问而导致数据不一致的问题。
jDK对synchronized锁进行升级:

5.1 偏向锁(Biased Locking)

偏向锁是为了解决大多数情况下都是由同一线程多次获得锁的场景。当一个线程第一次访问一个同步代码块时,JVM会将对象头的Mark Word设置为偏向锁,并将线程ID保存其中。之后,该线程再次进入同步代码块时,无需再进行同步操作,直接获得锁。

这种优化避免了多线程竞争的开销,提高了单线程执行同步代码块的性能。

5.2 轻量级锁(Lightweight Locking)

当多个线程竞争同一个锁时,偏向锁会失效,这时JVM使用轻量级锁进行优化。轻量级锁的核心思想是,使用CAS操作尝试在对象头的Mark Word中存储锁记录,而不是直接争夺锁。

第一个获得锁的线程会在对象头中记录锁信息,后续线程通过CAS更新锁记录。如果CAS成功,线程获得锁;否则,升级为重量级锁。

5.3 重量级锁(Heavyweight Locking)

在极端情况下,多个线程同时竞争同一个锁,轻量级锁无法解决竞争,JVM会将锁升级为重量级锁。这时,JVM使用操作系统提供的互斥量来保证同一时刻只有一个线程能够获得锁。

虽然重量级锁提供了最强的同步性,但相对于偏向锁和轻量级锁,它的性能开销较大。

6、结论

synchronized是Java中用于解决多线程同步问题的基本机制之一。通过深入理解synchronized的基本概念、对象级别的锁和类级别的锁、同步代码块、避免死锁以及性能考虑,我们可以更好地编写安全且高效的多线程程序。在实际应用中,根据具体场景选择合适的同步机制是至关重要的。

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

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

相关文章

【华为数据之道学习笔记】3-2 基础数据治理

基础数据用于对其他数据进行分类,在业界也称作参考数据。基础数据通常是静态的(如国家、币种),一般在业务事件发生之前就已经预先定义。它的可选值数量有限,可以用作业务或IT的开关和判断条件。当基础数据的取值发生变…

5G下行链路中的MIMO

5G MIMO 影响5G MIMO配置的主要因素是天线的数量和层数UE和gNB有一些预定义的表来定义天线端口和层的数量,选择了特定的表,UE如何确定表中的哪一行用于gNB的每次传输DCI 1-1中该规定了Antenna port 和 层数DMRS 端口数表示正在使用的天线数量&#xff0…

波奇学Linux:Linux进程状态,进程优先级

编写一个程序模拟进程 查看进程状态 修改代码后发现进程状态为由S变成R R为运行态,S为阻塞态 第一次为S是因为调用了外设(printf调用屏幕外设),实际上应该为R,S状态轮换,但是R太快了,所以每次…

中国区县人工智能企业数量,shp/excel格式,数据全,覆盖2010-2023年

基本信息. 数据名称: 中国区县人工智能企业数量 数据格式: Shpexcel 数据时间: 2010-2023年 数据几何类型: 面 数据坐标系: WGS84 数据来源:网络公开数据 数据字段:见【吧唧数据】www.bajidata.com 1a2023人工智能企业数量(个&…

模块二——滑动窗口:3.无重复字符的最长子串

文章目录 题目描述算法原理解法⼀:暴⼒求解(不会超时,可以通过)解法二:滑动窗口 代码实现解法⼀:暴⼒求解(时间复杂度为O(N^2^),空间复杂度为O(1))解法二:滑动窗口(时间复杂度为O(N)…

LeetCode-合并有序链表问题

合并两个有序链表 题目描述: 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 思路: 首先建立一个头节点方便后续操作,然后开始循环将两个链表的节点值进行比较,如果list1节…

hbuiler中使用npm安装datav

注:datav边框样式目前使用时:适用于网页,不适用于app 1、先安装node 安装、配置Node路径 2、为Node配置环境变量 3、在hbuilder的设置中填写node的路径 配置 4、打开cmd输入npm install jiaminghi/data-view 安装dataV,&…

MicroSD 卡 使用读卡器 读取速度测试

设备 - - 电脑为m.2固态硬盘 usb口为USB3.2 gen2接口(即支持1GB/s的接口) cpu: amd3600 测试方案1 直接MicroSD卡放入读卡器测试 38MB/s 从sd卡复制到本地C盘 测试方案2 MicroSD卡使用闪迪的SD卡套套上之后一起插入读卡器 76MB/s 从sd卡复制到本地C盘

uni-app应用设置 可以根据手机屏幕旋转进行 (横/竖) 屏切换

首先 我们打开项目的 manifest.json 在左侧导航栏中找到 源码视图 然后找到 app-plus 配置 在下面加上 "orientation": [//竖屏正方向"portrait-primary",//竖屏反方向"portrait-secondary",//横屏正方向"landscape-primary",//横屏…

Mybatis核心配置文件加载流程详解

Mybatis核心配置文件加载流程详解 本文将介绍MyBatis在配置文件加载的过程中,如何加载核心配置文件、如何解析映射文件中的SQL语句以及每条SQL语句如何与映射接口的方法进行关联。 映射配置文件 在介绍核心配置文件加载流程前,先给出一个简单的MyBati…

ROS gazebo 机器人仿真,环境与robot建模,添加相机 lidar,控制robot运动

b站上有一个非常好的ros教程234仿真之URDF_link标签简介-机器人系统仿真_哔哩哔哩_bilibili,推荐去看原视频。 视频教程的相关文档见:6.7.1 机器人运动控制以及里程计信息显示 Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程 本文对视频教程…

Docker入门指南:从基础到实践

在当今软件开发领域,Docker已经成为一种不可或缺的工具。通过将应用程序及其依赖项打包成轻量级的容器,Docker实现了开发、测试和部署的高度一致性。本文将深入研究Docker的基本概念,并通过详细的示例代码演示如何应用这些概念于实际场景中。…

基于FPGA的视频接口之高速IO(光纤)

简介 对于高速IO口配置光纤,现在目前大部分开发板都有配置,且也有说明,在此根据自己的工作经验以及对于各开发板的说明归纳 通过高速IO接口,以及硬件配置,可以实现对于光纤的收发功能,由于GTX的速率在500Mbs到10Gbps之间,但通道高速io可配置光纤10G硬件,物理通完成,则…

掌握 Python sympy 库的高级计算技巧

更多资料获取 📚 个人网站:ipengtao.com Sympy是Python中一个强大的符号计算库,为数学和科学计算提供了丰富的功能。本文将深入介绍Sympy库的各项功能,并提供丰富的示例代码,以帮助大家更好地理解和应用这一工具。 S…

【论文精读】REACT: SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS

REACT: SYNERGIZING REASONING AND ACTING IN LANGUAGE MODELS 前言ABSTRACT1 INTRODUCTION2 REACT: SYNERGIZING REASONING ACTING3 KNOWLEDGE-INTENSIVE REASONING TASKS3.1 SETUP3.2 METHODS3.3 RESULTS AND OBSERVATIONS 4 DECISION MAKING TASKS5 RELATED WORK6 CONCLUSI…

SpringBoot整合ActiveMQ

SpringBoot整合ActiveMQ 文章目录 SpringBoot整合ActiveMQ下载与安装SpringBoot整合ActiveMQ导坐标改配置,默认的保存位置生产与消费消息实现监听类——实现消息自动消费监听器转发消息:流程性业务消息消费完转入下一个消息队列 下载与安装 https://act…

8、操作符重载

友元 可以通过friend关键字,把一个全局函数、另一个类的成员函数或者另一个类整体,声明为授权类的友元友元拥有访问授权类任何非公有成员的特权友元声明可以出现在授权类的公有、私有或者保护等任何区域且不受访问控制限定符的约束友元不是成员&#xf…

Qt + MySQL(简单的增删改查)

Qt编译MySql插件教程 帮助: SQL Programming QSqlDatabase 静态函数 1.drivers(),得到可以使用的数据库驱动名字的集合 [static] QStringList QSqlDatabase::drivers();2.addDatabase(),添加一个数据库实例 [static] QSqlDatabase QSql…

慢SQL诊断

最近经常遇到技术开发跑来问我慢SQL优化相关工作,所以干脆出几篇SQL相关优化技术月报,我这里就以公司mysql一致的5.7版本来说明下。 在企业中慢SQL问题进场会遇到,尤其像我们这种ERP行业。 成熟的公司企业都会有晚上的慢SQL监控和预警机制。…

Logstash输入Kafka输出Es配置

Logstash介绍 Logstash是一个开源的数据收集引擎,具有实时管道功能。它可以从各种数据源中动态地统一和标准化数据,并将其发送到你选择的目的地。Logstash的早期目标主要是用于收集日志,但现在的功能已经远远超出这个范围。任何事件类型都可…