当前位置: 首页 > 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…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)

HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

基于数字孪生的水厂可视化平台建设:架构与实践

分享大纲&#xff1a; 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年&#xff0c;数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段&#xff0c;基于数字孪生的水厂可视化平台的…...

Caliper 配置文件解析:config.yaml

Caliper 是一个区块链性能基准测试工具,用于评估不同区块链平台的性能。下面我将详细解释你提供的 fisco-bcos.json 文件结构,并说明它与 config.yaml 文件的关系。 fisco-bcos.json 文件解析 这个文件是针对 FISCO-BCOS 区块链网络的 Caliper 配置文件,主要包含以下几个部…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

STM32HAL库USART源代码解析及应用

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

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...

macOS 终端智能代理检测

&#x1f9e0; 终端智能代理检测&#xff1a;自动判断是否需要设置代理访问 GitHub 在开发中&#xff0c;使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新&#xff0c;例如&#xff1a; fatal: unable to access https://github.com/ohmyzsh/oh…...

图解JavaScript原型:原型链及其分析 | JavaScript图解

​​ 忽略该图的细节&#xff08;如内存地址值没有用二进制&#xff09; 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么&#xff1a;保存在堆中一块区域&#xff0c;同时在栈中有一块区域保存其在堆中的地址&#xff08;也就是我们通常说的该变量指向谁&…...

从零手写Java版本的LSM Tree (一):LSM Tree 概述

&#x1f525; 推荐一个高质量的Java LSM Tree开源项目&#xff01; https://github.com/brianxiadong/java-lsm-tree java-lsm-tree 是一个从零实现的Log-Structured Merge Tree&#xff0c;专为高并发写入场景设计。 核心亮点&#xff1a; ⚡ 极致性能&#xff1a;写入速度超…...