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指令,而是三条。
读取i的值;
对i进行+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关键字,为了维护高并发是出现的原子性问题。技术是把双刃剑,多线程并发给我带来了前所未有的速率,然而在享受快速编程的过程,也给我们带来了原子性问题。如下: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的缩写…...
算法练习(特辑)设计算法的常用思想
1、递推法 递推的思想是把一个复杂的庞大的计算过程转换为简单过程的多次重复,每一次推导的结果作为下一次推导的开始。 2、递归法 递归算法实际上是把问题转化成规模更小的同类子问题,先解决子问题,再通过相同的求解过程逐步解决更高层次…...

哈希->模拟实现+位图应用
致前行路上的人: 要努力,但不要着急,繁花锦簇,硕果累累都需要过程! 目录 1. unordered系列关联式容器 1.1 unordered_map 1.1.1概念介绍: 1.1.2 unordered_map的接口说明 1.2unordered_set 1.3常见面试题oj…...

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

Linux 练习四 (目录操作 + 文件操作)
文章目录1 基于文件指针的文件操作1.1 文件的创建,打开和关闭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:Universally Unique Lexicographically Sortable Identifier(通用唯一词典分类标识符)UUID:Universally Unique Identifier(通用唯一标识符)为什么不选择UUIDUUID 目前有 5 个版本:版本1&a…...
java基础面试10题
1.JVM、JRE 和 JDK 的关系 Jvm:java虚拟机,类似于一个小型的计算机,它能够将java程序编译后的.class 文件解释给相应平台的本地系统执行,从而实现跨平台。 jre:是运行java程序所需要的环境的集合,它包含了…...
Golang闭包问题及并发闭包问题
目录Golang闭包问题及并发闭包问题匿名函数闭包闭包可以不传入外部参数,仍然可以访问外部变量闭包提供数据隔离并发闭包为什么解决方法Golang闭包问题及并发闭包问题 参考原文链接:https://blog.csdn.net/qq_35976351/article/details/81986496 htt…...

基频的后处理
基频归一化 基频为什么要归一化?为了消除人际随机差异,提取恒定参数,在语际变异中找到共性。 引言 声调的主要载体就是基频。但是对声调的感知会因人而异,例如某个听感上的高升调,不同的调查人员可能会分别描写成 […...
vue3 toRefs详解
简介 toRefs函数的作用是将响应式对象中的所有属性转换为单独的响应式数据,对象成为普通对象,并且值是关联的。在这个过程中toRefs会做以下两件事: 把一个响应式对象转换成普通对象对该普通对象的每个属性都做一次ref操作,这样每…...

Spring——AOP是什么?如何使用?
一、什么是AOP?在不修改源代码的情况下 增加功能二、底层是什么?动态代理aop是IOC的一个扩展功能,现有IOC,再有AOP,只是在IOC的整个流程中新增的一个扩展点而已: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 使用官网提供的源地址(方法一)2.1.2 使用epel的方式进行安装(方法二)2.2 开始安装nginx2.3 启动并进行测试2.4 其他的一些用法:三. 编译方式安装ngin…...

【FMCW 01】中频IF信号
FMCW信号 调频连续波(frequency modulated continuous wave,FMCW)顾名思义,就是对信号的频率进行线性调制的信号。 从时域上看,对频率的调制,就像一把连续的锯齿波。其中每一个锯齿叫做一个chirp,其持续的时间叫做ch…...
【蓝桥杯试题】暴力枚举题型
💃🏼 本人简介:男 👶🏼 年龄:18 🤞 作者:那就叫我亮亮叭 📕 专栏:蓝桥杯试题 文章目录1. 统计方形(数据加强版)1. 1 题目描述1.2 思路…...

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

UE实现相机聚焦物体功能
文章目录 1.实现目标2.实现过程2.1 实现原理2.2 源码浅析2.3 具体代码2.3.1 蓝图实现2.3.2 C++实现3.参考资料1.实现目标 实现根据输入的Actor,自动计算出其缩放显示到当前屏幕上相机的最终位置,然后相机飞行过去,实现相机对物体的聚集效果,避免每次输入FlyTo坐标参数,GI…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...