深入学习锁--Synchronized各种使用方法

一、什么是synchronized

在Java当中synchronized通常是用来标记一个方法或者代码块。在Java当中被synchronized标记的代码或者方法在同一个时刻只能够有一个线程执行被synchronized修饰的方法或者代码块。因此被synchronized修饰的方法或者代码块不会出现数据竞争的情况,也就是说被synchronized修饰的代码块是并发安全的。synchronized是java内置关键字,是内置锁,JVM中内置了,颗粒度比较大

二、synchronized的四种用法

2.1、修饰一个代码块

被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象;

2.2、修饰一个方法

 被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

非静态方法使用 synchronized 修饰的写法,修饰实例方法时,锁定的是当前实例对象

2.3、修饰一个静态的方法

其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

2.4、修饰一个类

其作用的范围是synchronized后面括号括起来的部分,作用对象是这个类的所有对象

三、使用案例分析

3.1、修饰一个方法

class SyncDemo {private int count;public void add() {count++;}public static void main(String[] args) throws InterruptedException {SyncDemo syncDemo = new SyncDemo();Thread t1 = new Thread(()->{for (int i = 0; i <1000 ; i++) {syncDemo.add();}});Thread t2 = new Thread(()->{for (int i = 0; i <1000 ; i++) {syncDemo.add();}});t1.start();t2.start();t1.join();  //线程的阻塞方法,线程t1执行完毕,再执行main主线程打印语句t2.join();  //线程的阻塞方法,线程t2执行完毕,再执行main主线程打印语句System.out.println(syncDemo.count);}
}

由于add方法没有使用synchronized修饰,线程t1和线程t2可能同时去执行add方法,那么就会导致最终count的结果小于20000,因为count++操作不具备原子性。 

将上述add方法被synchronized修饰

 public synchronized void add() {count++;}

由于add方法被synchronized修饰,因此每一个时刻只能有一个线程执行add方法,因此上面打印的结果是20000

总结: 

synchronized修饰的add方法一个时刻只能有一个线程执行的意思是对于一个SyncDemo类的对象来说一个时刻只能有一个线程进入。比如现在有两个SyncDemo的对象s1s2,一个时刻只能有一个线程进行s1add方法一个时刻只能有一个线程进入s2add方法,但是同一个时刻可以有两个不同的线程执行s1s2add方法,也就说s1add方法和s2add是没有关系的一个线程进入s1add方法并不会阻止另外的线程进入s2add方法,也就是说synchronized在修饰一个非静态方法的时候“锁”住的只是一个实例对象并不会“锁”住其它的对象。其实这也很容易理解,一个实例对象是一个独立的个体别的对象不会影响他,他也不会影响别的对象。

3.2、修饰一个静态的方法

class SyncDemo {private static int count;  //静态变量public static synchronized void add() { //静态方法count++;}public static void main(String[] args) throws InterruptedException {SyncDemo syncDemo = new SyncDemo();Thread t1 = new Thread(()->{for (int i = 0; i <1000 ; i++) {syncDemo.add();}});Thread t2 = new Thread(()->{for (int i = 0; i <1000 ; i++) {syncDemo.add();}});t1.start();t2.start();t1.join();  //线程的阻塞方法,线程t1执行完毕,再执行main主线程打印语句t2.join();  //线程的阻塞方法,线程t2执行完毕,再执行main主线程打印语句System.out.println(syncDemo.count);  //输出结果为2000}
}

上面的代码最终输出的结果也是20000,但是与前一个程序不同的是。这里的add方法用static修饰的,在这种情况下真正只能有一个线程进入到add方法,因为用static修饰的add方法是静态方法,静态方法所有对象公共的方法,因此和前面的那种情况不同,不存在两个不同的线程同一时刻执行不同实例对象的add方法。

你仔细想想如果能够让两个不同的线程执行add代码块,那么count++的执行就不是原子的了。那为什么没有用static修饰的代码为什么可以呢?因为当没有用static修饰时,每一个对象的count都是不同的,内存地址不一样,因此在这种情况下count++这个操作仍然是原子的!

3.3、修饰一个代码块

class SyncDemo {private  int count;  //静态变量public  void add() {System.out.println("进入了add方法");synchronized (this){count++;}}public  void minus() {System.out.println("进入了minus方法");synchronized (this){count--;}}public static void main(String[] args) throws InterruptedException {SyncDemo syncDemo = new SyncDemo();Thread t1 = new Thread(()->{for (int i = 0; i <10 ; i++) {syncDemo.add();}});Thread t2 = new Thread(()->{for (int i = 0; i <10 ; i++) {syncDemo.minus();}});t1.start();t2.start();t1.join();  //线程的阻塞方法,线程t1执行完毕,再执行main主线程打印语句t2.join();  //线程的阻塞方法,线程t2执行完毕,再执行main主线程打印语句System.out.println(syncDemo.count);  //输出结果为0}
}

有时候我们并不需要用synchronized修饰一个方法因为这样并发度就比较低了,一个方法一个时刻只能有一个线程在执行。因此我们可以选择用synchronized去修饰代码块只让某个代码块一个时刻只能有一个线程执行,除了这个代码块之外的代码还是可以并行的。

比如上面的代码当中addminus方法没有使用synchronized进行修饰,因此一个时刻可以有多个线程执行这个两个方法。在上面的synchronized代码块当中我们使用了this对象作为锁对象,只有拿到这个锁对象的线程才能够进入代码块执行,而在同一个时刻只能有一个线程能够获得锁对象。也就是说add函数和minus函数用synchronized修饰的两个代码块同一个时刻只能有一个代码块的代码能够被一个线程执行,因此上面的结果同样是0

这里说的锁对象是this也就SyncDemo类的一个实例对象,因为它锁住的是一个实例对象,因此当实例对象不一样的时候他们之间是没有关系的,也就是说不同实例用synchronized修饰的代码块是没有关系的,他们之间是可以并发的。

3.4、修饰一个类

class SyncDemo {private  int count;  //静态变量public  void add() {System.out.println("进入了add方法");synchronized (SyncDemo.class){count++;}}public  void minus() {System.out.println("进入了minus方法");synchronized (SyncDemo.class){count--;}}public static void main(String[] args) throws InterruptedException {SyncDemo syncDemo = new SyncDemo();Thread t1 = new Thread(()->{for (int i = 0; i <10 ; i++) {syncDemo.add();}});Thread t2 = new Thread(()->{for (int i = 0; i <10 ; i++) {syncDemo.minus();}});t1.start();t2.start();t1.join();  //线程的阻塞方法,线程t1执行完毕,再执行main主线程打印语句t2.join();  //线程的阻塞方法,线程t2执行完毕,再执行main主线程打印语句System.out.println(syncDemo.count);  //输出结果为0}
}

上面的代码是使用synchronized修饰类,锁对象是SyncDemo.class,这个时候他不再是锁住一个对象了,而是一个类了,这个时候的并发度就变小了,上一份代码当锁对象是SyncDemo的实例对象时并发度更大一些,因为当锁对象是实例对象的时候只有实例对象内部是不能够并发的,实例之间是可以并发的。但是当锁对象是SyncDemo.class的时候,实例对象之间时不能够并发的,因为这个时候的锁对象是一个类。

四、Synchronized与可见性和重排序

4.1互斥性/排他性(非公平锁)

synchronized(非公平锁)会起到互斥效果,某个线程执⾏到某个对象的 synchronized 中时,其他线程如果也执⾏到同⼀个对象 synchronized 就会阻塞等待。

  • 进⼊ synchronized 修饰的代码块, 相当于加锁。
  • 退出 synchronized 修饰的代码块, 相当于解锁。

PS:公平锁 VS 非公平锁
公平锁:按资排辈,先到先得。需要“唤醒”这一步操作,会牺牲一定的性能。(线程发现锁占用,尝试获取锁一段时间后,进入休眠状态,进入排队队列中等待。上一个线程释放锁后,会唤醒排队等候的其他线程中最前面的线程从阻塞状态又切换至运行状态)
非公平锁:来得早不如来得巧,不需要“唤醒”,性能高。(线程1发现锁占用,尝试获取锁一段时间后,进入休眠状态。此时线程2来了,处于运行状态,尝试获取锁,此时刚好上一个线程释放了锁,那么线程2直接得到了锁并去运行它的任务了。排队等待的其他线程获取锁的顺序不是按照访问的顺序先来先到的,而是由线程自己竞争,随机获取到锁)Java里所有的锁默认是非公平锁。

4.2可见性

  • 当一个线程进入到synchronized同步代码块的时候,将会刷新所有对该线程的可见的变量,也就是说如果其他线程修改了某个变量,而且线程需要在Synchronized代码块当中使用,那就会重新刷新这个变量到内存当中,保证这个变量对于执行同步代码块的线程是可见的。

  • 当一个线程从同步代码块退出的时候,也会将线程的工作内存同步到内存当中,保证在同步代码块当中修改的变量对其他线程可见。

4.3可重入性(可重入性锁)

synchronized 同步块对同⼀条线程来说是可重⼊的,不会出现⾃⼰把⾃⼰锁死的问题。

/*** synchronized 可重入性测试*/
public class ThreadSynchronized4 {public static void main(String[] args) {synchronized (ThreadSynchronized4.class) {System.out.println("当前主线程已经得到了锁"); //当执行到此行代码时,表示已经获得锁synchronized (ThreadSynchronized4.class) { //同一个线程获取锁两次System.out.println("当前主线程再次得到了锁"); //若两行代码都能打印,说明具备可重入性}}}
}

 

五、synchronized实现原理

(面试必问)synchronized是如何实现的?
①在Java代码层面:

synchronized加锁的对象里有一个的隐藏的对象头,这个对象头(可看作一个类)里有很多属性,其中比较关注的两个属性是:是否加锁的标识和拥有当前锁的线程id。

每次进⼊ synchronized 修饰的代码块时,会去对象头中先判断加锁的标识,再判断拥有当前锁的线程id,从而决定当前线程能否往下继续执行。

判断加锁标识为false->对象头未加锁,当前线程可以进入synchronized 修饰的代码块,并设置加锁标识为true,设置拥有当前锁的线程id为此线程id。
判断加锁标识为true->对象头已加锁,需进一步判断拥有当前锁的线程id是否为此线程id,若是,则继续往下执行;否则,不能往下执行,需要等待锁资源释放后重新竞争再获取锁。
②在JVM层面和操作系统层面:

synchronized同步锁是通过JVM内置的Monitor监视器实现的,而监视器又是依赖操作系统的互斥锁Mutex实现的。↓
————————————————
原文链接:https://blog.csdn.net/WWXDwrn/article/details/129115774

六、synchronized历史发展进程

  • 在JDK1.6之前(多使用Lock)synchronized使用很少,那时synchronized默认使用重量级锁实现,所以性能较差。
  • 在JDK1.6时,synchronized做了优化。锁升级流程如下:

1,无锁:没有线程访问时默认是无锁状态,加了synchronized也是无锁状态。有线程访问时才加锁。更大程度上减少锁带来的程序上的开销。
2,偏向锁:当有一个线程访问时会升级为偏向锁。(在对象头里存了这样一把锁,后面再来线程时会判断,如果线程id和拥有锁的线程id相同,会让它进去,只偏向某一个线程,其他线程来了之后要继续等)
3,轻量级锁:当有多个线程访问时会升级为轻量级锁。
4,重量级锁:当大量线程访问同时竞争锁资源的时候会升级为重量级锁。

原文链接:https://baijiahao.baidu.com/s?id=1740505389877266267&wfr=spider&for=pc

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

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

相关文章

Hazel引擎学习(十二)

我自己维护引擎的github地址在这里&#xff0c;里面加了不少注释&#xff0c;有需要的可以看看 参考视频链接在这里 Scene类重构 参考&#xff1a;《InsideUE4》GamePlay架构&#xff08;二&#xff09;Level和World 目前我的Scene类基本只是给entt的封装&#xff0c;提供了…

工业4.0分类:数字化转型的多维度

引言 工业4.0代表着制造业的数字化革命&#xff0c;它将制造过程带入了数字时代。然而&#xff0c;工业4.0并不是一个单一的概念&#xff0c;而是一个多维度的范畴&#xff0c;包括不同的技术、应用领域、企业规模和实施方式。但在这一多维度的概念中&#xff0c;低代码技术正…

如何优雅地使用Mybatis逆向工程生成类

文/朱季谦 1.环境&#xff1a;SpringBoot 2.在pom.xml文件里引入相关依赖&#xff1a; 1 <plugin>2 <groupId>org.mybatis.generator</groupId>3 <artifactId>mybatis-generator-maven-plugin</artifactId>4 <version>1.3.6<…

《三十》模块化打包构建工具 Rollup

19的2小时06分钟 Rollup 是一个 JavaScript 的模块化打包工具&#xff0c;可以帮助编译微小的代码到庞大的复杂的代码中&#xff08;例如一个库或者一个应用程序&#xff09;。 Rollup 和 Webpack 的区别&#xff1a; Rollup 也是一个模块化的打包工具&#xff0c;但是它主要…

排序:非递归的快排

目录 非递归的快排&#xff1a; 代码分析&#xff1a; 代码演示&#xff1a; 非递归的快排&#xff1a; 众所周知&#xff0c;递归变成非递归&#xff0c;而如果还想具有递归的功能&#xff0c;那么递归的那部分则需要变成循环来实现。 而再我们的排序中&#xff0c;我们可…

Azure Machine Learning - 使用 Azure OpenAI 服务生成图像

在浏览器/Python中使用 Azure OpenAI 生成图像&#xff0c;图像生成 API 根据文本提示创建图像。 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕&#xff0c;复旦机器人智能实验室成员&#x…

【动态规划】【广度优先】LeetCode2258:逃离火灾

作者推荐 本文涉及的基础知识点 二分查找算法合集 动态规划 二分查找 题目 给你一个下标从 0 开始大小为 m x n 的二维整数数组 grid &#xff0c;它表示一个网格图。每个格子为下面 3 个值之一&#xff1a; 0 表示草地。 1 表示着火的格子。 2 表示一座墙&#xff0c;你跟…

pytorch:YOLOV1的pytorch实现

pytorch&#xff1a;YOLOV1的pytorch实现 注&#xff1a;本篇仅为学习记录、学习笔记&#xff0c;请谨慎参考&#xff0c;如果有错误请评论指出。 参考&#xff1a; 动手学习深度学习pytorch版——从零开始实现YOLOv1 目标检测模型YOLO-V1损失函数详解 3.1 YOLO系列理论合集(Y…

Redis对象类型检测与命令多态

一. 命令类型 Redis中操作键的命令可以分为两类。 一种命令可以对任意类型的键执行&#xff0c;比如说DEL&#xff0c;EXPIRE&#xff0c;RENAME&#xff0c;TYPE&#xff0c;OBJECT命令等。 举个例子&#xff1a; #字符串键 127.0.0.1:6379> set msg "hello world&…

第76讲:MySQL数据库中常用的命令行工具的基本使用

文章目录 1.mysql客户端命令工具2.mysqladmin管理数据库的客户端工具3.mysqlbinlog查看数据库中的二进制日志4.mysqlshow统计数据库中的信息5.mysqldump数据库备份工具6.mysqllimport还原备份的数据7.source命令还原SQL类型的备份文件 MySQL数据库提供了很多的命令行工具&#…

python 画条形图(柱状图)

目录 前言 基础介绍 月度开支的条形图 前言 条形图&#xff08;bar chart&#xff09;&#xff0c;也称为柱状图&#xff0c;是一种以长方形的长度为变量的统计图表&#xff0c;长方形的长度与它所对应的变量数值呈一定比例。 当使用 Python 画条形图时&#xff0c;通常会使…

vscode 编译运行c++ 记录

一、打开文件夹&#xff0c;新建或打开一个cpp文件 二、ctrl shift p 进入 c/c配置 进行 IntelliSense 配置。主要是选择编译器、 c标准&#xff0c; 设置头文件路径等&#xff0c;配置好后会生成 c_cpp_properties.json&#xff1b; 二、编译运行&#xff1a; 1、选中ma…

zabbix 通过 odbc 监控 mssql

1、环境 操作系统&#xff1a;龙蜥os 8.0 zabbix&#xff1a;6.0 mssql&#xff1a;2012 2、安装odbc 注意&#xff1a;需要在zabbix server 或者 zabbix proxy 安装 odbc驱动程序 dnf -y install unixODBC unixODBC-devel3、安装mssql驱动程序 注意&#xff1a;我最开始尝试…

Tomcat管理功能使用

前言 Tomcat管理功能用于对Tomcat自身以及部署在Tomcat上的应用进行管理的web应用。在默认情况下是处于禁用状态的。如果需要开启这个功能&#xff0c;需要配置管理用户&#xff0c;即配置tomcat-users.xml文件。 &#xff01;&#xff01;&#xff01;注意&#xff1a;测试功…

react 学习笔记 李立超老师 | (学习中~)

文章目录 react学习笔记01入门概述React 基础案例HelloWorld三个API介绍 JSXJSX 解构数组 创建react项目(手动)创建React项目(自动) | create-react-app事件处理React中的CSS样式内联样式 | 内联样式中使用state (不建议使用)外部样式表 | CSS Module React组件函数式组件和类组…

不同品牌的手机如何投屏到苹果MacBook?例如小米、华为怎样投屏比较好?

习惯使用apple全家桶的人当然知道苹果手机或iPad可以直接用airplay投屏到MacBook。 但工作和生活的多个场合里&#xff0c;并不是所有人都喜欢用同一品牌的设备&#xff0c;如果同事或同学其他品牌的手机需要投屏到MacBook&#xff0c;有什么方法可以快捷实现&#xff1f; 首先…

【GDB】

GDB 1. GDB调试器1.1 前言1.2 GDB编译程序1.3 启动GDB1.4 载入被调试程序1.5 查看源码1.6 运行程序1.7 断点设置1.7.1 通过行号设置断点1.7.2 通过函数名设置断点1.7.3 通过条件设置断点1.7.4 查看断点信息1.7.5 删除断点 1.8 单步调试1.9 2. GDB调试core文件2.1 设定core文件的…

(五)五种最新算法(SWO、COA、LSO、GRO、LO)求解无人机路径规划MATLAB

一、五种算法&#xff08;SWO、COA、LSO、GRO、LO&#xff09;简介 1、蜘蛛蜂优化算法SWO 蜘蛛蜂优化算法&#xff08;Spider wasp optimizer&#xff0c;SWO&#xff09;由Mohamed Abdel-Basset等人于2023年提出&#xff0c;该算法模型雌性蜘蛛蜂的狩猎、筑巢和交配行为&…

iOS(swiftui)——系统悬浮窗( 可在其他应用上显示,可实时更新内容)

因为ios系统对权限的限制是比较严格的,ios系统本身是不支持全局悬浮窗(可在其他app上显示)。在iphone14及之后的iPhone机型中提供了一个叫 灵动岛的功能,可以在手机上方可以添加一个悬浮窗显示内容并实时更新,但这个功能有很多局限性 如:需要iPhone14及之后的机型且系统…

Java面试遇到的一些常见题

目录 1. Java语言有几种基本类型&#xff0c;分别是什么&#xff1f; 整数类型&#xff08;Integer Types&#xff09;&#xff1a; 浮点类型&#xff08;Floating-Point Types&#xff09;&#xff1a; 字符类型&#xff08;Character Type&#xff09;&#xff1a; 布尔类…