深入理解 Java 中的 synchronized 代码块

目录

前言

一、synchronized的工作原理

二、使用synchronized代码块的场景

三、编写synchronized代码块的最佳实践

四、何时使用 synchronized 代码块?

同步:

不同步:

五、Demo讲解

1.使用synchronized代码块减小锁的粒度,提高性能

2.synchronized可以使用任意的object进行加锁

 3.不要使用String的常量加锁,会出现死循环问题

4.锁对象的改变问题

5.死锁问题  


前言

        在多线程编程中,确保共享资源的安全访问是一个关键问题。Java 提供了多种机制来实现线程同步,其中 synchronized 关键字是最常用的一种。在本文中,我们将深入探讨 synchronized 代码块的使用和原理,并通过示例展示其应用。

一、synchronized的工作原理

synchronized关键字可以应用于方法或代码块,以确保同一时间只有一个线程可以执行被synchronized修饰的代码。其工作原理主要基于Java对象头中的Monitor(监视器)。每个Java对象都有一个与之关联的Monitor,线程在访问synchronized代码块时,需要首先获得该对象的Monitor的所有权。

  • synchronized方法:当一个线程进入一个对象的synchronized(this)方法时,它自动获取该对象的Monitor的所有权,并在方法返回或抛出异常时释放Monitor。
  • synchronized代码块synchronized代码块允许我们更精细地控制哪些代码需要被同步。线程在访问synchronized(object)代码块时,需要获得指定对象object的Monitor的所有权。

二、使用synchronized代码块的场景

  • 保护共享资源:当多个线程需要访问和修改同一份数据时,可以使用synchronized代码块来确保同一时间只有一个线程能够访问这些数据。
  • 避免死锁:与synchronized方法相比,synchronized代码块提供了更细粒度的同步控制,有助于减少死锁的风险。通过只同步必要的代码段,可以减少线程之间的竞争和等待时间。
  • 提高性能:在某些情况下,使用synchronized代码块可以避免不必要的同步开销。例如,当一个方法中的大部分代码都不需要同步时,可以将需要同步的代码段封装在synchronized代码块中。

三、编写synchronized代码块的最佳实践

  • 尽量减小同步范围:只将需要同步的代码段放在synchronized代码块中,避免不必要的同步开销。
  • 避免在同步块中调用可能阻塞的方法:在同步块中调用可能阻塞的方法(如IO操作、等待用户输入等)会导致其他等待Monitor的线程也被阻塞,从而降低系统的并发性能。
  • 注意锁的粒度:过细的锁粒度可能导致线程之间的竞争加剧,而过粗的锁粒度则可能导致不必要的同步开销。因此,在选择锁的粒度时需要权衡这两个因素。
  • 避免嵌套锁:尽量避免在一个已经持有某个对象Monitor的线程中再次请求该对象或其他对象的Monitor。这可能导致死锁或其他并发问题。
  • 考虑使用ReentrantLock等高级并发工具:虽然synchronized关键字简单易用,但在某些复杂场景下可能需要更灵活的同步控制。此时可以考虑使用Java并发包中的ReentrantLock、Semaphore等高级并发工具。

四、何时使用 synchronized 代码块?

同步:

  1. 访问共享资源:当多个线程需要访问同一个对象或变量时,应该使用同步。
  2. 修改共享状态:当多个线程修改同一个对象的状态时,应该使用同步。
  3. 执行原子操作:当需要保证某些操作的原子性(即操作不可分割)时,应该使用同步。

不同步:

  1. 只读操作:如果多个线程只是读取共享资源,而不修改它,不需要同步。
  2. 局部变量:局部变量是线程私有的,不需要同步。
  3. 无共享资源:如果多个线程操作的资源相互独立,不需要同步。

五、Demo讲解

1.使用synchronized代码块减小锁的粒度,提高性能

使用synchronized声明的方法在某些情况下是有弊端的,比如A线程调用同步的方法执行一个很长时间的任务,那么B线程就必须等待比较长的时间才能执行,这样的情况下可以使用synchronized代码块去优化代码执行时间,也就是通常所说的减小锁的粒度。

package com.ctb.sync6;/*** 使用synchronized代码块减小锁的粒度,提高性能* * @author biao** 2024年*/
public class Optimize {public void doLongTimeTask(){try {System.out.println("当前线程开始:" + Thread.currentThread().getName() + ", 正在执行一个较长时间的业务操作,其内容不需要同步");Thread.sleep(2000);synchronized(this){System.out.println("当前线程:" + Thread.currentThread().getName() + ", 执行同步代码块,对其同步变量进行操作");Thread.sleep(1000);}System.out.println("当前线程结束:" + Thread.currentThread().getName() +", 执行完毕");} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {final Optimize otz = new Optimize();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {otz.doLongTimeTask();}},"t1");Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {otz.doLongTimeTask();}},"t2");t1.start();t2.start();}}

结果: 

:使用 synchronized 代码块减小锁的粒度,以提高多线程程序的性能。在 doLongTimeTask() 方法中,通过将长时间业务操作与需要同步的操作分别放置在不同的代码块中,两个线程可以更有效地并发执行需要同步的操作,可以减小锁的粒度,使得只有在必要的部分才会被同步,从而提高了并发执行的效率。

这样做的好处是在确保线程安全的同时,尽量减少同步的范围,避免不必要的阻塞,从而提升程序的性能。也是多线程编程中常用的优化手段之一

2.synchronized可以使用任意的object进行加锁

package com.ctb.sync6;/*** 使用synchronized代码块加锁,比较灵活* * @author biao** 2024年*/
public class ObjectLock {public void method1(){synchronized (this) {	//对象锁try {System.out.println("do method1..");Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}public void method2(){		//类锁synchronized (ObjectLock.class) {try {System.out.println("do method2..");Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}private Object lock = new Object();public void method3(){		//任何对象锁synchronized (lock) {try {System.out.println("do method3..");Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {final ObjectLock objLock = new ObjectLock();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {objLock.method1();}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {objLock.method2();}});Thread t3 = new Thread(new Runnable() {@Overridepublic void run() {objLock.method3();}});t1.start();t2.start();t3.start();}}

结果: 

 注:在多线程环境下使用 synchronized 代码块来实现不同类型锁的加锁操作,通过展示对象锁、类锁和任意对象锁的使用方式,说明了不同加锁方式在多线程并发环境中的作用,用法比较灵活。

 3.不要使用String的常量加锁,会出现死循环问题

package com.ctb.sync6;
/*** synchronized代码块对字符串的锁,注意String常量池的缓存功能* * @author biao** 2024年*/
public class StringLock {public void method() {//new String("字符串常量")synchronized ("字符串常量") {try {while(true){System.out.println("当前线程 : "  + Thread.currentThread().getName() + "开始");Thread.sleep(1000);		System.out.println("当前线程 : "  + Thread.currentThread().getName() + "结束");}} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {final StringLock stringLock = new StringLock();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {stringLock.method();}},"t1");Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {stringLock.method();}},"t2");t1.start();t2.start();}
}

 结果:

注:StringLock 类中,定义了一个 method() 方法,该方法使用 "字符串常量" 作为锁对象来实现同步操作。在方法中,通过 synchronized("字符串常量") 来对代码块进行加锁,确保多个线程在执行该代码块时是互斥的。

main 方法中,创建了两个线程 t1 和 t2 分别执行 method() 方法。由于两个线程共享同一个字符串常量作为锁对象,因此它们在执行 method() 方法时会相互竞争这个锁。  

public void method() {//new String("字符串常量")synchronized (new String("字符串常量")) {try {while(true){System.out.println("当前线程 : "  + Thread.currentThread().getName() + "开始");Thread.sleep(1000);		System.out.println("当前线程 : "  + Thread.currentThread().getName() + "结束");}} catch (InterruptedException e) {e.printStackTrace();}}}

 结果:

注:“字符串常量”它是只有一个引用,尽量不要拿字符串常量这种方式去加锁,我们可以使用new String("字符串常量"),注意即可  

4.锁对象的改变问题

当使用一个对象进行加锁的时候,要注意对象本身发生改变的时候,那么持有的锁就不同,如果对象本身不发生改变,那么依然是同步的,即使是对象的属性发生了改变。  

package com.ctb.sync6;
/*** 锁对象的改变问题* * @author biao** 2024年*/
public class ChangeLock {private String lock = "lock";private void method(){synchronized (lock) {try {System.out.println("当前线程 : "  + Thread.currentThread().getName() + "开始");lock = "change lock";Thread.sleep(2000);System.out.println("当前线程 : "  + Thread.currentThread().getName() + "结束");} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {final ChangeLock changeLock = new ChangeLock();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {changeLock.method();}},"t1");Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {changeLock.method();}},"t2");t1.start();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}t2.start();}}

结果:

注:当我们使用字符串常量作为一把锁的时候,一定要注意在synchronized代码块里尽量不要去修改该锁对象的内容,第一个线程拿到的锁lock的值是lock,第二个线程拿到的是change lock去获得锁  

private void method(){synchronized (lock) {try {System.out.println("当前线程 : "  + Thread.currentThread().getName() + "开始");//lock = "change lock";Thread.sleep(2000);System.out.println("当前线程 : "  + Thread.currentThread().getName() + "结束");} catch (InterruptedException e) {e.printStackTrace();}}}

结果:

注://lock = "change lock";当我们不去修改该锁对象的内容时,他将会依次去获取锁。  

package com.ctb.sync6;
/*** 同一对象属性的修改不会影响锁的情况* * @author biao** 2024年*/
public class ModifyLock {private String name ;private int age ;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public synchronized void changeAttributte(String name, int age) {try {System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 开始");this.setName(name);this.setAge(age);System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 修改对象内容为: " + this.getName() + ", " + this.getAge());Thread.sleep(2000);System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 结束");} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {final ModifyLock modifyLock = new ModifyLock();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {modifyLock.changeAttributte("张三", 20);}},"t1");Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {modifyLock.changeAttributte("李四", 21);}},"t2");t1.start();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}t2.start();}}

 结果:

注:由于 changeAttribute 方法使用了对象锁,因此在多线程环境下,只有一个线程能够执行该方法,保证了对同一个对象的属性修改操作是互斥的。一个对象里面的属性发生改变的时候,是不影响锁的变化的,还是这个对象。

使用对象锁实现了对同一对象属性的修改操作的线程安全性,确保了多线程环境下对对象属性的修改操作是同步的,避免了数据不一致的情况发生  

5.死锁问题  

package com.ctb.sync6;/*** 死锁问题,在设计程序时就应该避免双方相互持有对方的锁的情况* * @author biao** 2024年*/
public class DeadLock implements Runnable{private String tag;private static Object lock1 = new Object();private static Object lock2 = new Object();public void setTag(String tag){this.tag = tag;}@Overridepublic void run() {if(tag.equals("a")){synchronized (lock1) {try {System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock1执行");Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock2) {System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入lock2执行");}}}if(tag.equals("b")){synchronized (lock2) {try {System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入了lock2执行");Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock1) {System.out.println("当前线程 : "  + Thread.currentThread().getName() + " 进入了lock1执行");}}}}public static void main(String[] args) {DeadLock d1 = new DeadLock();d1.setTag("a");DeadLock d2 = new DeadLock();d2.setTag("b");Thread t1 = new Thread(d1, "t1");Thread t2 = new Thread(d2, "t2");t1.start();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}t2.start();}}

 结果:

注:run() 方法中,如果 tag 的取值为 "a",则线程会先获取 lock1,然后尝试获取 lock2;如果 tag 的取值为 "b",则线程会先获取 lock2,然后尝试获取 lock1

当两个线程同时运行时,它们会陷入死锁状态,因为彼此持有对方需要的锁而无法释放,导致程序无法继续执行下去,最终需要手动终止程序。

为避免死锁,应该设计程序避免多个线程竞争多个锁的情况,或者确保多个锁的获取顺序是一致的,从而避免循环等待的情况发生。  

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

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

相关文章

51 USART数据收发

1.0 USART实现单个数据收发 串口启动之前需要对串口进行初始化,主要是设置产生波特率的定时器1,使用串口的工作方式还是中断的工作方式具体的配置步骤如下所示。 注: 1: 确定TMOD (定时器模式寄存器) 确…

2021年9月电子学会青少年软件编程 中小学生Python编程等级考试三级真题解析(判断题)

2021年9月Python编程等级考试三级真题解析 判断题(共10题,每题2分,共20分) 26、readline()执行结果为字符串,readlines()执行结果为列表 答案:对 考点分析:考查文件读操作,readli…

省去烦恼!轻松实现一台电脑登录多个微信号的秘诀揭秘!

你知道如何在同一台电脑上登录多个微信号,并实现聚合聊天吗? 今天,我将分享一个多微管理神器——个微管理系统,帮助你解决这一问题! 1、多号同时登录,聚合聊天 无论你有多少个微信号,都可以一…

yolov8通过训练完成的模型生成图片热力图--论文需要

源代码来自于网络 使用pytorch_grad_cam,对特定图片生成热力图结果。 安装热力图工具 pip install pytorch_grad_cam pip install grad-cam# get_params中的参数: # weight: # 模型权重文件,代码默认是yolov8m.pt # c…

【机器学习】集成学习方法:Bagging与Boosting的应用与优势

🔥 个人主页:空白诗 文章目录 引言一、集成学习的定义二、Bagging方法1. 随机森林(Random Forest)2. 其他Bagging方法 二、Boosting方法1. 梯度提升树(Gradient Boosting Machine, GBM)解释GBM的基本原理和…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 特惠寿司(100分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 📎在线评测链接 特惠寿司(100分) 🌍 评测功能需要订阅专栏后私信联系清隆解…

9. 文本三剑客之awk

文章目录 9.1 什么是awk9.2 awk命令格式9.3 awk执行流程11.4 行与列11.4.1 取行11.4.2 取列 9.1 什么是awk 虽然sed编辑器是非常方便自动修改文本文件的工具,但其也有自身的限制。通常你需要一个用来处理文件中的数据的更高级工具,它能提供一个类编程环…

【二】【动态规划NEW】91. 解码方法,62. 不同路径,63. 不同路径 II

91. 解码方法 一条包含字母 A-Z 的消息通过以下映射进行了 编码 : ‘A’ -> “1” ‘B’ -> “2” … ‘Z’ -> “26” 要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法&#xff…

利用74HC165实现8路并行输入口的扩展

代码&#xff1a; #include <mega16.h>// Declare your global variables here #define hc165_clk PORTB.0 #define hc165_lp PORTB.1 #define hc165_out PINB.2unsigned char read_hc165(void) {unsigned char data0,i,temp0x80;hc165_lp0;hc165_lp1; for(i0;i<7;i)…

Git 基础操作(一)

Git 基础操作 配置Git 安装完Git后&#xff0c;首先要做的事情是设置你的 用户名 和 e-mail 地址。这样在你向仓库提交代码的时候&#xff0c;就知道是谁提交的&#xff0c;以及提交人的联系方式。 配置用户名和邮箱 使用git config [--global] user.name "你的名字&qu…

碳中和研究院OLED透明屏2x2整机项目方案

一、项目背景 随着全球气候变化和环境问题的日益严重&#xff0c;碳中和成为各国政府和企业的重要议题。为了响应这一趋势&#xff0c;黑龙江碳中和研究院计划引入先进的OLED透明屏技术&#xff0c;以展示其研究成果和碳中和理念。本项目旨在为该研究院提供一套高质量的OLED透明…

干部选拔任用的六条原则

在干部选拔任用的过程中&#xff0c;为确保选拔出的干部能够真正符合党和人民的期望&#xff0c;必须遵循以下六条原则&#xff1a; 一、党管干部原则 党管干部原则是指在整个干部选拔任用过程中&#xff0c;党要发挥总揽全局、协调各方的领导作用&#xff0c;确保选拔出的干…

pytorch 加权CE_loss实现(语义分割中的类不平衡使用)

加权CE_loss和BCE_loss稍有不同 1.标签为long类型&#xff0c;BCE标签为float类型 2.当reduction为mean时计算每个像素点的损失的平均&#xff0c;BCE除以像素数得到平均值&#xff0c;CE除以像素对应的权重之和得到平均值。 参数配置torch.nn.CrossEntropyLoss(weightNone,…

算法01 递推算法及相关问题详解【C++实现】

目录 递推的概念 训练&#xff1a;斐波那契数列 解析 参考代码 训练&#xff1a;上台阶 参考代码 训练&#xff1a;信封 解析 参考代码 递推的概念 递推是一种处理问题的重要方法。 递推通过对问题的分析&#xff0c;找到问题相邻项之间的关系&#xff08;递推式&a…

【机器学习】LightGBM: 优化机器学习的高效梯度提升决策树

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 LightGBM: 优化机器学习的高效梯度提升决策树引言一、LightGBM概览二、核心技术…

Go语言结构体内嵌接口

前言 在golang中&#xff0c;结构体内嵌结构体&#xff0c;接口内嵌接口都很常见&#xff0c;但是结构体内嵌接口很少见。它是做什么用的呢&#xff1f; 当我们需要重写实现了某个接口的结构体的(该接口)的部分方法&#xff0c;可以使用结构体内嵌接口。 作用 继承赋值给接口…

激活和禁用Hierarchy面板上的物体

1、准备工作&#xff1a; (1) 在HIerarchy上添加待隐藏/显示的物体&#xff0c;名字自取。如&#xff1a;endImage (2) 在Inspector面板&#xff0c;该物体的名称前取消勾选&#xff08;隐藏&#xff09; (3) 在HIerarchy上添加按钮&#xff0c;名字自取。如&#xff1a;tip…

chatgpt的命令词

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

汽车级TPSI2140QDWQRQ1隔离式固态继电器,TMUX6136PWR、TMUX1109PWR、TMUX1133PWR模拟开关与多路复用器(参数)

1、TPSI2140-Q1 是一款隔离式固态继电器&#xff0c;专为高电压汽车和工业应用而设计。 TPSI2140-Q1 与 TI 具有高可靠性的电容隔离技术和内部背对背 MOSFET 整合在一起&#xff0c;形成了一款完全集成式解决方案&#xff0c;无需次级侧电源。 该器件的初级侧仅由 9mA 的输入电…

CSS实现经典打字小游戏《生死时速》

&#x1f33b; 前言 CSS 中有这样一个模块&#xff1a;Motion Path 运动模块&#xff0c;它可以使元素按照自定义的路径进行移动。本文将为你讲解这个模块属性的使用&#xff0c;并且利用它实现我小时候电脑课经常玩的一个打字游戏&#xff1a;金山打字的《生死时速》。 &…