深入学习锁--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的对象s1和s2,一个时刻只能有一个线程进行s1的add方法,一个时刻只能有一个线程进入s2的add方法,但是同一个时刻可以有两个不同的线程执行s1和s2的add方法,也就说s1的add方法和s2的add是没有关系的,一个线程进入s1的add方法并不会阻止另外的线程进入s2的add方法,也就是说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去修饰代码块,只让某个代码块一个时刻只能有一个线程执行,除了这个代码块之外的代码还是可以并行的。比如上面的代码当中
add和minus方法没有使用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
相关文章:
深入学习锁--Synchronized各种使用方法
一、什么是synchronized 在Java当中synchronized通常是用来标记一个方法或者代码块。在Java当中被synchronized标记的代码或者方法在同一个时刻只能够有一个线程执行被synchronized修饰的方法或者代码块。因此被synchronized修饰的方法或者代码块不会出现数据竞争的情况&#x…...
pycharm中绘制一个3D曲线
import numpy as np import matplotlib.pyplot as plt # 中文的设置 import matplotlib as mp1 from mpl_toolkits.mplot3d import Axes3D mp1.rcParams["font.sans-serif"] ["kaiti"] mp1.rcParams["axes.unicode_minus"] False # 数据创建 X…...
人工智能_AI服务器安装清华开源_CHATGLM大语言模型_GLM-6B安装部署_人工智能工作笔记0092
看到的这个开源的大模型,很牛,~关键让我们自己也可以部署体验一把了,虽然不知道具体内部怎么构造的但是,也可以自己使用也挺好. 可以部署在自己的机器上也可以部署在云服务器上. 安装以后,是可以使用python代码进行提问,然后返回结果的,这样就可以实现我们自己的chat应用了, …...
用户反馈组件实现(Vue3+ElementPlus)含图片拖拽上传
用户反馈组件实现(Vue3ElementPlus)含图片拖拽上传 1. 页面效果1.1 正常展示1.2 鼠标悬浮1.3 表单 2. 代码部分1.2 html、ts1.2 less部分 3. 编码过程遇到的问题 1. 页面效果 1.1 正常展示 1.2 鼠标悬浮 1.3 表单 2. 代码部分 1.2 html、ts <templ…...
K8S部署nginx并且使用NFS存储数据
安装NFS 在master安装NFS systemctl start nfs-server修改配置 /etc/exports /data *(rw,no_root_squash,no_all_squash,sync)目录为 /data 允许所有地址访问 验证下 [rootmaster nginx]# showmount -e 192.168.57.61 Export list for 192.168.57.61: /data *共享可以正常…...
Homework 3: Higher-Order Functions, Self Reference, Recursion, Tree Recursion
Q1: Compose 编写一个高阶函数composer,它返回两个函数func和func_adder。 func是一个单参数函数,它应用到目前为止已经组合的所有函数。这些函数将首先应用最新的函数(参见doctests和示例)。 func_adder用于向我们的组合添加更多…...
(C++)有效三角形的个数--双指针法
个人主页:Lei宝啊 愿所有美好如期而遇 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://le…...
11.30BST理解,AVL树操作,定义;快速幂,二分求矩阵幂(未完)
完全二叉树结点的度可能有1,满二叉树的度只能为0或2 BST构建 BST是左孩子都比根节点小,右孩子都比根节点大 二叉搜索树的插入,删除,调整 平衡树理解 任何一个平衡二叉树,它的中序遍历都是一样的,都是有…...
深入理解Java核心技术:Java工程师的实用干货笔记
💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】🤟 基于Web端打造的:👉轻量化工具创作平台💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】 在Java工程师的职业生涯中,深入理解…...
大学里面转专业介绍
目录 个人情况转专业过程中的经验分享转专业后的学习建议和心态调整转专业后的时间平衡 个人情况 信息科学与工程学院计算机科学与技术专业2019级本科生,曾从物理与微电子科学学院后转入信息科学与技术学院。学习成绩连续三年专业前10% 项目:爬虫项目、…...
MySQL_1. mysql数据库介绍
shell脚本差不多快完结了接下来会为大家更新MySQL系列的相关的基础知识笔记,希望对大家有所帮助,好废话不多说,接下来开始正题! 1.mysql数据库介绍 mysql 是一款安全、跨平台、高效的,并与 PHP、Java 等主流编程语言…...
TimeGPT:时间序列预测模型实例
时间序列预测领域正在经历一个非常激动人心的时期。在过去的三年里,我们见证了许多重要的贡献,如N-BEATS、N-HiTS、PatchTST和TimesNet等。同时,大型语言模型(LLM)近来在流行度方面取得了很大的成功,例如Ch…...
【JavaEE】多线程 (1)
目录 1. 认识线程(Thread) 1) 线程是什么 2) 为啥要有线程 3) 进程和线程的区别 2.第⼀个多线程程序 3.多线程的其他创建方式 方法二:实现 Runnable 接⼝ 方法三:匿名内部类 方法四:实现Runable, 重写run, 匿名内部类 方法五:使用lambda表达式…...
linux 应用层同步与互斥机制之条件变量
2、条件变量 互斥量防止多个线程同时访问同一共享变量。(我们称为互斥) 有一种情况,多个线程协同工作。一个线程的消费需要等待另一个线程的产出。必须线程B完成了应有的任务,满足了某一个条件,线程A才能继续执行。&…...
3.5毫米音频连接器接线方式
3.5毫米音频连接器接线方式 耳机插头麦克风插头 绘制电路图注意事项 3.5毫米音频连接器分为单声道开关型和无开关型如下图: sleeve(套筒) tip(尖端) ring(环) 耳机插头 麦克风插头 绘制电路图…...
智慧农田可视化大数据综合管理平台方案,EasyCVR助力农业高质量发展
一、背景需求 我国是农业大国,农业耕地面积达到20亿亩。随着物联网、大数据、人工智能等新一代信息技术与农业农村加速融合,以及国家对农业的重视,智慧农业对于我国农业现代化建设和实施乡村振兴战略具有重大引领与推动作用。在传统农田生产…...
python超详细基础文件操作【建议收藏】
文章目录 前言1 文件操作1.1 文件打开与关闭1.1.1 打开文件1.1.2 关闭文件 1.2 访问模式及说明 2 文件读写2.1 写数据(write)2.2 读数据(read)2.3 读数据(readlines)2.3 读数据(readline&#x…...
华为变革进展指数TPM的五个级别:试点级、推行级、功能级、集成级和世界级
华为变革进展指数TPM的五个级别:试点级、推行级、功能级、集成级和世界级 TPM(Transformation Progress Metrics,变革进展指标)用来衡量管理体系在华为的推行程度和推行效果,并找出推行方面的不足与问题,…...
vue el-select多选封装及使用
使用了Element UI库中的el-select和el-option组件来构建多选下拉框。同时,也包含了一个el-input组件用于过滤搜索选择项,以及el-checkbox-group和el-checkbox组件用于显示多选项。 创建组件index.vue (src/common-ui/selectMultiple/index.vue) <tem…...
大模型上下文学习(ICL)训练和推理两个阶段31篇论文
大模型都火了这么久了,想必大家对LLM的上下文学习(In-Context Learning)能力都不陌生吧? 以防有的同学不太了解,今天我就来简单讲讲。 上下文学习(ICL)是一种依赖于大型语言模型的学习任务方式…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...
门静脉高压——表现
一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构:由肠系膜上静脉和脾静脉汇合构成,是肝脏血液供应的主要来源。淤血后果:门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血,引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...

