当前位置: 首页 > news >正文

synchronized从入门到踹门

synchronized是什么

synchronized是Java关键字,为了维护高并发是出现的原子性问题。技术是把双刃剑,多线程并发给我带来了前所未有的速率,然而在享受快速编程的过程,也给我们带来了原子性问题。

如下:

public class Main {private static int i = 0;public static void main(String[] args) throws InterruptedException {Main main = new Main();Thread a = new Thread(() -> {main.add10K();}, "A");  // 线程AThread b = new Thread(() -> {main.add10K();}, "B");  // 线程Ba.start();  // 启动线程Ab.start();  // 启动线程Ba.join();   // 等待线程A执行完毕b.join();   // 等待线程B执行完毕System.out.println(i); // 打印i的值,期望20000}// +10000操作public void add10K(){for (int j = 0; j < 10000; j++) {i++;}}
}

上面的程序,你细细品味一下结果会是多少?然后再回来看下面的结果。或者自己编程一下上面的代码,然后带着思考运行一下(面试高频点)。






下面的分析请耐心看,并思考。这就是面试要跟面试官聊的东西,聊越多,聊越细,证明你思考得越多。

答案是小于20000,其实了解过JVM的同学都知道,i++在CPU中其实不是一条CPU指令,而是三条。

  1. 读取i的值;

  1. 对i进行+1操作;

  1. 装载i的值。

那么多线程并发,其实就是每个线程分配一个时间片执行,时间片执行完毕后就轮到下一个线程。在上面的程序,可能会发生的事情:当线程A做到第2步的时候(对i进行+1操作),可能时间片得分给线程B了,此时线程A和线程B假设都读到i的值为0,这时线程B对i进行了+1操作后i的值为1,然后轮到线程A执行,线程A此时到了第三步,把刚才i+1的值装载回去(i=1)。问题就在这了,期望两个线程对i都+1后,期望值应该为2,然而此时却为1。这种情况还不少见,所以导致最终的结果小于期望值20000。

那么怎么解决这个问题呢?通过上面得分析我们知道,就是操作系统搞着时间片轮转运行造成的,不要轮转不就行了,确实可以。但是如果这么做了,又回到单线程时代,况且现在已经不是单核时代了,每个人得电脑至少双核起步吧,所以思路是对的,但是现实场景是骨感的。那么有没有一个可能,就是在线程A对i进行+1操作的时候,我把i这个参数给他锁住,先不要让别的线程操作它呢?这就对了,现在的synchronized、Lock就是这个思想,在操作某个变量时,我先在这个变量前面加个"栅栏"(也可以理解成锁),只有当我撤了这个栅栏(或者撤了这把锁),其他人才可以对这个变量进行操作,这不就没什么问题了。

synchronized其实就是利用这个原理做的这个关键字,但是它是隐式的,没有展现出来,但是其实在底层的"汇编指令",它其实是有展现的,带你们看一下。


这是Java代码

public class Main {public static void main(String[] args) throws InterruptedException {}public void operate(){synchronized(this){}}}

这是"汇编指令",JVM自己约定的汇编指令,所以我加了双引号。(这个是通过:Javap -c Main.class指令得到的,大家有兴趣可以试试!)

可以看到上图,我画圈圈的东西,monitorenter、monitorexit、monitorexit,这其实就是synchronized的两个隐式"锁"指令了,monitorenter代表加锁,monitorexit代表解锁。为什么monitorexit有两个呢?原因其实也很简单,为了预防死锁用的,因为我们正常情况下当然是一个解锁就可以了,万一没运行到解锁那一行,程序挂了呢?那此时是不是在异常时设置一条解锁会好点?所以两个monitorexit是有道理的!


synchronized作用范围

锁非静态方法

public class Main {public static void main(String[] args) throws InterruptedException {}public synchronized void operate(){}}

像上面的程序,锁的就是方法,这个方法是来源某个实例的,所以根据传递原则,其实锁的就是你new出来的那个实例,应该很好理解。下面来个例子,带你走走坑。

这个程序代码务必认真看,比你看100篇synchronized讲解有用!因为很多都是走马观花,没有落实到具体实践讲解,只让你知道锁的是实例,而实际场景中遇到的坑,你可能自己都理所当然,不知所以。


public class Main {public static void main(String[] args) throws InterruptedException {A a = new A();B b = new B();a.addMoney(b.money);}}class A {public Integer money = 100;public synchronized void addMoney(Integer targetMoney){money += targetMoney;System.out.println(money);}}class B {public Integer money = 200;}

问题:假设在执行addMoney方法的时候,有其他线程修改了B的money为300,那么addMoney执行的结果是什么呢?

思考一下,可以评论区说一下答案+理解,这个真的很重要。这是synchronized最关键的点了,我先设个坑,评论区回答认真看的,因为真的很重要、很重要、很重要。


非静态代码块


public class Main {public static void main(String[] args) throws InterruptedException {A a = new A();B b = new B();a.addMoney(b.money);}}class A {public Integer money = 100;public void addMoney(Integer targetMoney){synchronized(this){money += targetMoney;System.out.println(money);}}}class B {public Integer money = 200;}

跟锁非静态方法其实一样的,就是锁的实例,也存在上面的问题,所以说他真的很重要,笔试很容易就把分丢了,面试很容易就把印象说没了。


public class Main {public static void main(String[] args) throws InterruptedException {A a = new A();B b = new B();a.addMoney(b.money);}}class A {public Integer money = 100;public void addMoney(Integer targetMoney){synchronized(Main.class){money += targetMoney;System.out.println(money);}}}class B {public Integer money = 200;}

这个就不一样了哦,我换成了Main.class,说明锁的是对象,那么有关该对象的变量和方法都会被锁住哦,其他形成访问该类的东西时,都会阻塞,等待该线程释放锁。


锁静态方法


public class Main {public static void main(String[] args) throws InterruptedException {A a = new A();B b = new B();a.addMoney(b.money);}}class A {public Integer money = 100;public static void addMoney(Integer targetMoney){synchronized(Main.class){}}}class B {public Integer money = 200;}

这个跟锁静对象一样,锁的就是类,解释如上。


synchronized的优化

JDK1.6之后,JDK有对synchronized关键字进行了优化,主要是做了一些锁升级的过程:无锁--偏向锁--轻量级锁--重量级锁。

好好读下文,这个也很重要,不懂评论区留言,看到必回!

反向思考一下,加这个synchronized是为了干嘛?不就是为了当某个线程操作某个变量的时候,不然其他线程操作该变量吗?那就是阻塞咯。这个阻塞其实也就是我们上面一直讲解的重量级锁,确实一开始就是这样(JDK1.6之前)。那这很损耗性能的耶,所以搞JDK那群家伙就开始想办法优化这些思想了,我把synchronized做成一个动态化锁。


无锁

如果程序不会造成线程安全的,那我把synchronized去掉,变成无锁化。如下程序,只对i进行读操作,我锁它干嘛?


public class Main {public static void main(String[] args) {A a = new A();a.readI();}}class A {public Integer i = 100;public synchronized void readI(){System.out.println(i);}}

看似有锁,其实我们从"汇编指令"看,已经被JDK偷偷优化成无锁了。


偏向锁

但是实际场景可不是一直读哦,也会有某个线程一直在那里频繁的写写写,但是也无所谓啦,以为就你这个线程是把,那我就在实例对象头那里,直接把偏向锁ID,设置成你这个线程ID就可以了,只要是你这个家伙来访问这个变量,我直接也把锁优化掉。

Idea开启偏向锁VM参数:-XX:+UseBiasedLocking,偏向锁开启后,默认是4秒才会生效


没有等4秒直接用,没使用到偏向锁(non-biasable)

public class Main {public static void main(String[] args) throws InterruptedException {//        TimeUnit.SECONDS.sleep(5);A a = new A();new Thread(()->{a.writeI();}).start();// 打印一下加锁后的实例a的对象头信息System.out.println(ClassLayout.parseInstance(a).toPrintable());}}class A {public Integer i = 100;public synchronized void writeI(){i += 1;}}

这里顺带说一下,对象头的打印是使用了ClassLayout工具类,可以在maven添加以下两个依赖使用

<dependencies><!--查看对象头工具--><dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version></dependency><dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.16</version></dependency>
</dependencies>

等待4秒,使用偏向锁,value为偏向锁ID

public class Main {public static void main(String[] args) throws InterruptedException {TimeUnit.SECONDS.sleep(5);A a = new A();new Thread(()->{a.writeI();}).start();// 打印一下加锁后的实例a的对象头信息System.out.println(ClassLayout.parseInstance(a).toPrintable());}}class A {public Integer i = 100;public synchronized void writeI(){i += 1;}}

轻量级锁

偏向锁其实指的是一般都是某个线程进行变量操作,但是实际场景其实是有多个线程进行操作的,因此在其他线程检查操作的对象头不是自己的ID时,通过CAS尝试再次获取锁,获取不到则转变成轻量级锁,获取到了就还是偏向锁。


重量级锁

这个场景一般是高并发时,都是重量级,因为有多个线程同时操作同个共享资源。如果按锁的锁的升级流程,无非就是浪费时间。


以上则是synchronized的所有概述,欢迎共勉。

相关文章:

synchronized从入门到踹门

synchronized是什么synchronized是Java关键字&#xff0c;为了维护高并发是出现的原子性问题。技术是把双刃剑&#xff0c;多线程并发给我带来了前所未有的速率&#xff0c;然而在享受快速编程的过程&#xff0c;也给我们带来了原子性问题。如下&#xff1a;public class Main …...

ubuntu-8-安装nfs服务共享目录

Ubuntu最新版本(Ubuntu22.04LTS)安装nfs服务器及使用教程 ubuntu16.04挂载_如何在Ubuntu 20.04上设置NFS挂载 Ubuntu 20.04 设置时区、配置NTP同步 timesyncd 代替 ntpd 服务器 10.0.2.11 客户端 10.0.2.121 NFS简介 (1)什么是NFS NFS就是Network File System的缩写&#xf…...

算法练习(特辑)设计算法的常用思想

1、递推法 递推的思想是把一个复杂的庞大的计算过程转换为简单过程的多次重复&#xff0c;每一次推导的结果作为下一次推导的开始。 2、递归法 递归算法实际上是把问题转化成规模更小的同类子问题&#xff0c;先解决子问题&#xff0c;再通过相同的求解过程逐步解决更高层次…...

哈希->模拟实现+位图应用

致前行路上的人&#xff1a; 要努力&#xff0c;但不要着急&#xff0c;繁花锦簇&#xff0c;硕果累累都需要过程&#xff01; 目录 1. unordered系列关联式容器 1.1 unordered_map 1.1.1概念介绍&#xff1a; 1.1.2 unordered_map的接口说明 1.2unordered_set 1.3常见面试题oj…...

苹果手机想要传输数据到电脑怎么传输呢?

苹果手机想要传输数据到电脑怎么传输呢&#xff1f;尤其是传输数据到Windows系统&#xff0c;可能需要使用一些传输软件&#xff0c;那么常用的传输软件有哪些呢&#xff1f;下文将为大家推荐几款常用的苹果手机数据传输常用工具。近期苹果发布了iPhone14系列手机&#xff0c;如…...

Linux 练习四 (目录操作 + 文件操作)

文章目录1 基于文件指针的文件操作1.1 文件的创建&#xff0c;打开和关闭1.2 文件读写操作2 基于文件描述符的文件操作2.1 打开、创建和关闭文件2.2 文件读写2.3 改变文件大小2.4 文件映射2.5 文件定位2.6 获取文件信息2.7 复制文件描述符2.8 文件描述符和文件指针2.9 标准输入…...

自学大数据第四天~hadoop集群的搭建

Hadoop集群安装配置 当hadoop采用分布式模式部署和运行时,存储采用分布式文件系统HDFS,此时HDFS名称节点和数据节点位于不同的机器上; 数据就可以分布到多个节点,不同的数据节点上的数据计算可以并行执行了,这时候MR才能发挥其本该有的作用; 没那么多机器怎么办~~~~多几个虚拟…...

ULID和UUID

ULID&#xff1a;Universally Unique Lexicographically Sortable Identifier&#xff08;通用唯一词典分类标识符&#xff09;UUID&#xff1a;Universally Unique Identifier&#xff08;通用唯一标识符&#xff09;为什么不选择UUIDUUID 目前有 5 个版本&#xff1a;版本1&a…...

java基础面试10题

1.JVM、JRE 和 JDK 的关系 Jvm&#xff1a;java虚拟机&#xff0c;类似于一个小型的计算机&#xff0c;它能够将java程序编译后的.class 文件解释给相应平台的本地系统执行&#xff0c;从而实现跨平台。 jre&#xff1a;是运行java程序所需要的环境的集合&#xff0c;它包含了…...

Golang闭包问题及并发闭包问题

目录Golang闭包问题及并发闭包问题匿名函数闭包闭包可以不传入外部参数&#xff0c;仍然可以访问外部变量闭包提供数据隔离并发闭包为什么解决方法Golang闭包问题及并发闭包问题 参考原文链接&#xff1a;https://blog.csdn.net/qq_35976351/article/details/81986496 ​ htt…...

基频的后处理

基频归一化 基频为什么要归一化&#xff1f;为了消除人际随机差异&#xff0c;提取恒定参数&#xff0c;在语际变异中找到共性。 引言 声调的主要载体就是基频。但是对声调的感知会因人而异&#xff0c;例如某个听感上的高升调&#xff0c;不同的调查人员可能会分别描写成 […...

vue3 toRefs详解

简介 toRefs函数的作用是将响应式对象中的所有属性转换为单独的响应式数据&#xff0c;对象成为普通对象&#xff0c;并且值是关联的。在这个过程中toRefs会做以下两件事&#xff1a; 把一个响应式对象转换成普通对象对该普通对象的每个属性都做一次ref操作&#xff0c;这样每…...

Spring——AOP是什么?如何使用?

一、什么是AOP&#xff1f;在不修改源代码的情况下 增加功能二、底层是什么&#xff1f;动态代理aop是IOC的一个扩展功能&#xff0c;现有IOC&#xff0c;再有AOP&#xff0c;只是在IOC的整个流程中新增的一个扩展点而已&#xff1a;BeanPostProcessorbean的创建过程中有一个步…...

【微服务】认识微服务

目录 1.1 单体、分布式、集群 单体 分布式 集群 1.2 系统架构演变 1.2.1 单体应⽤架构 1.2.2 垂直应⽤架构 1.2.3 分布式架构 1.2.4 SOA架构 1.2.5 微服务架构 1.3 微服务架构介绍 微服务架构的常⻅问题 1.4 SpringCloud介绍 1.4.1 SpringBoot和SpringCloud有啥关…...

【独家】华为OD机试 C 语言解题 - 最长连续子串

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...

【Linux】CentOS7操作系统安装nginx实战(多种方法,超详细)

文章目录前言一. 实验环境二. 使用yum安装nginx2.1 添加yum源2.1.1 使用官网提供的源地址&#xff08;方法一&#xff09;2.1.2 使用epel的方式进行安装&#xff08;方法二&#xff09;2.2 开始安装nginx2.3 启动并进行测试2.4 其他的一些用法&#xff1a;三. 编译方式安装ngin…...

【FMCW 01】中频IF信号

FMCW信号 调频连续波(frequency modulated continuous wave&#xff0c;FMCW)顾名思义&#xff0c;就是对信号的频率进行线性调制的信号。 从时域上看&#xff0c;对频率的调制&#xff0c;就像一把连续的锯齿波。其中每一个锯齿叫做一个chirp&#xff0c;其持续的时间叫做ch…...

【蓝桥杯试题】暴力枚举题型

&#x1f483;&#x1f3fc; 本人简介&#xff1a;男 &#x1f476;&#x1f3fc; 年龄&#xff1a;18 &#x1f91e; 作者&#xff1a;那就叫我亮亮叭 &#x1f4d5; 专栏&#xff1a;蓝桥杯试题 文章目录1. 统计方形&#xff08;数据加强版&#xff09;1. 1 题目描述1.2 思路…...

I.MX6ULL_Linux_系统篇(22) kernel移植

原厂 Linux 内核编译 NXP 提供的 Linux 源码肯定是可以在自己的 I.MX6ULL EVK 开发板上运行下去的&#xff0c;所以我们肯定是以 I.MX6ULL EVK 开发板为参考&#xff0c;然后将 Linux 内核移植到 I.MX6U-ALPHA 开发板上的。 配置编译 Linux 内核 和uboot一样&#xff0c;在编…...

UE实现相机聚焦物体功能

文章目录 1.实现目标2.实现过程2.1 实现原理2.2 源码浅析2.3 具体代码2.3.1 蓝图实现2.3.2 C++实现3.参考资料1.实现目标 实现根据输入的Actor,自动计算出其缩放显示到当前屏幕上相机的最终位置,然后相机飞行过去,实现相机对物体的聚集效果,避免每次输入FlyTo坐标参数,GI…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

docker 部署发现spring.profiles.active 问题

报错&#xff1a; org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

【JavaSE】多线程基础学习笔记

多线程基础 -线程相关概念 程序&#xff08;Program&#xff09; 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c;操作系统就会为该进程分配内存…...

STM32HAL库USART源代码解析及应用

STM32HAL库USART源代码解析 前言STM32CubeIDE配置串口USART和UART的选择使用模式参数设置GPIO配置DMA配置中断配置硬件流控制使能生成代码解析和使用方法串口初始化__UART_HandleTypeDef结构体浅析HAL库代码实际使用方法使用轮询方式发送使用轮询方式接收使用中断方式发送使用中…...