synchronized 与 volatile 关键字
目录
- 1.前言
- 1.synchronized 关键字
- 1. 互斥
- 2.保证内存可见性
- 3.可重入
- 2. volatile 关键字
- 1.保证内存可见性
- 2.无法保证原子性
- 3.synchronized 与 volatile 的区别
1.前言
synchronized
关键字和volatile
是大家在Java
多线程学习时接触的两个关键字,很多同学可能学习完就忘记了,本文帮助大家回顾以及学习两个关键字的作用,以及说出它们的区别,同时也为了自己学习巩固。
1.synchronized 关键字
1. 互斥
属于synchronized
最关键的特性,可以起到互斥的作用,当某个线程执行到某个对象的synchronized
中时,其他线程如果也执行到同一个对象 的synchronized
时就会进行阻塞等待。
- 进入
synchronized
修饰的代码块此时相当于 加锁 - 退出
synchronized
修饰的代码块此时相当于 释放锁
其解决的问题是在多线程环境下,多个线程对于同一个变量进行读写操作时可能产生的线程安全问题。
如下图代码:
public class Main {static int count = 0;static void add() {count++;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {add();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {add();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}
按照预期,我们希望count
的值应该是100000
,当执行后输出的答案却是:
无论多次执行多少次,答案总是与预期相差甚远。这是最简单的多线程安全问题。因为count++
这个操作,需要先将count
从主内存读入到工作内存,然后增值,再将更改后的值写回主内存,这一系列操作必须保证原子性,而在多线程环境下是无法保证的,所以我们需要加上synchronized
进行上锁,以此保证自增这个操作的原子性。
只需更改add
方法如下:
synchronized static void add() {count++;}
再次运行后答案与预期相符合:
需要注意一点,synchronized
修饰方法时如果是静态方法,则加的是该类对象的锁,如果是成员方法,则加的是对象锁。
2.保证内存可见性
从上面也可以看出synchronized
的工作过程:
- 1.获得互斥锁
- 2.从主内存拷贝变量的最新副本到工作内存
- 3.执行代码
- 4.讲更改后的共享变量的值更新回工作内存
- 5.释放互斥锁
这样的工作流程,是一定可以保证内存可见性的。当然有的同学并不了解什么是内存可见性,下文讲volatile
时我们稍微讲一下,因为它也能保证内存可见性。
3.可重入
synchronized
同步块,对于同一条线程来说是可重入的,不会出现将自身锁死的情况。
当然大家可能对 自身锁死 这个情况不太理解,我们举例一个代码:
public class Main {//锁对象public static Object lock = new Object();public static void main(String[] args) {//一次加锁synchronized (lock) {//二次加锁synchronized (lock) {System.out.println("正确输出");}}}
}
当线程在一次加锁时,会成功加锁,当第二次加锁时,此时lock
已被上锁,于是该线程进行阻塞等待,但其实这个锁是被它自己拿着的,它又不进行释放锁操作,于是将自己锁死。这样的锁称之为 不可重入锁。
当我们Java
中的synchronized
是可重入锁,不会出现上面的问题,它可以正确打印:
如果对上述代码还不够理解,可以再看一个二次加锁的例子:
public class Main {public int count = 0;synchronized void increase() {count++;}synchronized void increase2() {increase();}
}
在上诉代码中:
increase
和increase2
两个方法都加了synchronized
,而且它们的锁对象都是针对当前对象加锁的。- 在调用
increase2
时,会先给该对象上锁,执行调用increase
时,会二次上锁(此时上个锁还未释放),这是没问题的,因为synchronized
是可重入锁。
那是否真的上了两把锁呢?
其实并非如此,在可重入锁的内部,包含了 线程持有者 和 计数器 两个信息。
- 如果某个线程加锁时,发现锁已被占用,但又发现占用的恰好是自己时,那么然后可以获取到这个锁,并让计数器自增
- 解锁时首先会让计数器自减,但只有真正自减到
0
时,我们才会真正意义上的将该锁释放,以供其他线程获取到。
2. volatile 关键字
相对于 synchronized
来说,大家可能对volatile
会比较陌生,我们来看看其有哪些作用。
1.保证内存可见性
简单来说,线程在工作时,会去主内存中读取数据到工作内存中,然后从工作内存读取数据。但是,线程从工作内存读取数据的速度,要远远的大于从主内存读取数据。
当一个线程大量地从主内存请求同一个变量的值时,它会发现这个值一直没变,此时jvm
会 “自作主张” 的进行优化,直接从工作内存读取之前读到的值。这就会导致一个问题,其他线程对这个共享变量值进行修改,这个线程不能及时地被看到,也就读到了一个错误的值。
比如如下代码:
public class Main{static int isQuit = 0;public static void main(String[] args) {Thread t = new Thread(() -> {while (isQuit == 0) {}System.out.println("t线程执行结束");});t.start();Scanner sc = new Scanner(System.in);isQuit = sc.nextInt();System.out.println("main线程执行结束");}
}
执行以后随便输入一个非零整数:
发现t
线程仍然在进行,而main
线程已经结束,但按照逻辑其实此时isQuit
值被修改为非零,t
线程也应该结束。这就是由于内存可见性产生的问题,main
线程修改了isQuit
的值t
线程并不能及时的接收到。
解决的方法也很简单,只需要给isQuit
加上volatile
关键字,这样每次t
线程都会强制去主内存中读取isQuit
的值,从而保证了内存可见性。
2.无法保证原子性
volatile
相较于synchronized
来说,主要在于其无法保证原子性,也就是对于下面这个程序,即使给count
加上volatile
,我们也无法让count
的值为100000
。
public class Main {static int count = 0;static void add() {count++;}public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {add();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {add();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}
3.synchronized 与 volatile 的区别
根据上面的总结,我们可知:synchronized
既可以保证原子性还可以保证内存可见性,而volatile
只能保证内存可见性。那有的人是不是肯定想,那我们无脑使用synchronized
不就好了吗?
那肯定不对,synchronized
会进行加锁,使得效率大大的较低,而volatile
却不会影响效率,所以只有必须要保证原子性的操作,我们才选择synchronized
进行加锁。
相关文章:

synchronized 与 volatile 关键字
目录1.前言1.synchronized 关键字1. 互斥2.保证内存可见性3.可重入2. volatile 关键字1.保证内存可见性2.无法保证原子性3.synchronized 与 volatile 的区别1.前言 synchronized关键字和volatile是大家在Java多线程学习时接触的两个关键字,很多同学可能学习完就忘记…...

【0成本搭建个人博客】——Hexo+Node.js+Gitee Pages
目录 1、下载安装Git 2、下载安装Node.js 3、使用Hexo进行博客的搭建 4、更改博客样式 5、将博客上传到Gitee 6、更新博客 首先看一下Hexo的博客的效果。 1、下载安装Git Git 是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本…...
【面试实战】认证授权流程及原理分析
认证授权流程及原理分析 1、认证 (Authentication) 和授权 (Authorization)的区别是什么?2、什么是Cookie ? Cookie的作用是什么?如何在服务端使用 Cookie ?3、Cookie 和 Session 有什么区别?如何使用Session进行身份验证?1、认证 (Authentication) 和授权 (Authorizatio…...
TPM命令解析之tpm2_startauthsession
参考网址链接:tpm2-tools/tpm2_startauthsession.1.md at master tpm2-software/tpm2-tools GitHub 命令名称 tpm2_startauthsession 功能 启动一个TPM会话。 命令形式 tpm2_startauthsession [OPTIONS] 描述 启动一个TPM会话。默认是启动一个试验(…...

第14章 局部波动率模型
这学期会时不时更新一下伊曼纽尔德曼(Emanuel Derman) 教授与迈克尔B.米勒(Michael B. Miller)的《The Volatility Smile》这本书,本意是协助导师课程需要,发在这里有意的朋友们可以学习一下,思…...
云原生周刊:开源“赢了”,但它可持续吗?
日前召开的 State of Open 会议上,开源“赢了”,但如果政府和企业不站出来确保生态系统在未来的弹性和可持续性,那么它仍然会失败。 OpenUK 首席执行官 Amanda Brock 在开幕式上表示,数字化和开源在过去 5 到 10 年的进步提升了工…...

读《企业IT架构转型之道》
本书还没读完,暂摘抄一些概念,因为自身做的新系统也在转型,从单体式到一体化一年来遇到很多问题有技术上的,也有团队协作的,过程是痛苦且复杂的,所以在刚翻阅前几十页时候,对于淘宝技术团队转型…...

Qt中的QTcpSocket、QWebSocket和QLocalSocket
同时实现了QTcpSocket、QWebSocket和QLocalSocket的简单通讯deamon,支持自动获取本机ip,多个客户端交互。在这个基础上你可以自己加错误检测、心跳发送、包封装解析和客户端自动重连等功能。 获取本机电脑ip: QString Widget::getIp() {QSt…...

枚举学习贴
1. 概述 1.1 是什么 枚举对应英文(enumeration, 简写 enum)枚举是一组常量的集合。可以这里理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象 1.2 枚举的二种实现方式 自定义类实现枚举使用 enum 关键字实现枚举 1.3 什么时候用 存在有限…...

【C++】30h速成C++从入门到精通(继承)
继承的概念及定义继承的概念继承(inheritance)机制是面向对象程序设计使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序…...

Java多线程还不会的进来吧,为你量身打造
💗推荐阅读文章💗 🌸JavaSE系列🌸👉1️⃣《JavaSE系列教程》🌺MySQL系列🌺👉2️⃣《MySQL系列教程》🍀JavaWeb系列🍀👉3️⃣《JavaWeb系列教程》…...

8 神经网络及Python实现
1 人工神经网络的历史 1.1 生物模型 1943年,心理学家W.S.McCulloch和数理逻辑学家W.Pitts基于神经元的生理特征,建立了单个神经元的数学模型(MP模型)。 1.2 数学模型 ykφ(∑i1mωkixibk)φ(WkTXb)y_{k}\varphi\left(\sum_{i1…...

使用QIS(Quantum Image Sensor)图像重建总结(1)
最近看了不少使用QIS重建图像的文章,觉得比较完整详细的还是Abhiram Gnanasambandam的博士论文:https://hammer.purdue.edu/articles/thesis/Computer_vision_at_low_light/20057081 1 介绍 讲述了又墨子的小孔成像原理,到交卷相机…...

【SpringCloud】SpringCloud教程之Nacos实战(二)
目录前言一.Nacos实现配置管理二.Nacos拉取配置三.Nacos配置热更新(自动刷新,不需要重启服务)1.在有Value注入变量所在类添加注解2.新建类用于属性加载和配置热更新四.Nacos多环境配置共享1.多环境共享配置2.配置的加载优先级测试3.配置优先级前言 Nacos实战一&…...

利用Qemu工具仿真ARM64平台
Windows系统利用Qemu仿真ARM64平台0 写在最前1 Windows安装Qemu1.1 下载Qemu1.2 安装Qemu1.3 添加环境变量1.4测试安装是否成功2. Qemu安装Ubuntu-Server-Arm-642.1 安装前的准备2.2 安装Ubuntu server arm 64位镜像3 Windows配置Qemu网络和传输文件3.1 参考内容3.2 Windows安装…...

【Hello Linux】进程控制 (内含思维导图)
作者:小萌新 专栏:Linux 作者简介:大二学生 希望能和大家一起进步! 本篇博客简介:简单介绍下进程的控制 包括进程启动 进程终止 进程等待 进程替换等概念 进程控制介绍进程创建fork函数fork函数的返回值fork函数的使用…...

嵌入式linux物联网毕业设计项目智能语音识别基于stm32mp157开发板
stm32mp157开发板FS-MP1A是华清远见自主研发的一款高品质、高性价比的Linux单片机二合一的嵌入式教学级开发板。开发板搭载ST的STM32MP157高性能微处理器,集成2个Cortex-A7核和1个Cortex-M4 核,A7核上可以跑Linux操作系统,M4核上可以跑FreeRT…...

【黄河流域公安院校网络空间安全技能挑战赛】部分wp
文章目录webbabyPHPfunnyPHPEzphp**遍历文件目录的类**1、DirectoryIterator:2、FilesystemIterator:3、**Globlterator**读取文件内容的类:SplFileObjectMisc套娃web babyPHP <?php highlight_file(__FILE__); error_reporting(0);$num $_GET[nu…...

五点CRM系统核心功能是什么
很多企业已经把CRM客户管理系统纳入信息化建设首选,用于提升核心竞争力,改善企业市场、销售、服务、渠道和客户管理等几个方面,并进行创新或转型。CRM系统战略的五个关键要点是:挖掘潜在客户、评估和培育、跟进并成交、分析并提高…...

window.print() 前端实现网页打印详解
目录 前言 一、print()方法 二、打印样式 2.1使用打印样式表 2.2使用媒介查询 2.3内联样式使用media属性 2.4在css中使用import引入打印样式表 三、打印指定区域部分内容 3.1方法一 3.2方法二 3.3方法三 四、强制插入分页 4.1page-break-before(指定元素前…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...

Razor编程中@Html的方法使用大全
文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...