【Java并发】【synchronized】适合初学者体质入门的synchronized

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
📚欢迎订阅专栏,专栏别名《在2B工作中寻求并发是否搞错了什么》

文章目录

  • 前言
  • 入门
    • 什么是synchronized?
    • 为什么用synchronized?
    • 怎么用synchronized?
      • 修饰方法
      • 修饰代码块
  • syncrhonized在框架源码中的使用
    • Vector 和 Hashtable
    • StringBuffer
  • 总结
    • synchronized的优点
    • synchronized的缺点
    • synchronized的适用场景
  • 后话
  • 参考

前言

这一篇同样是sychronzied的入门篇,不会涉及底层原理和实现,很适合初学者的学习。好了, 不废话了,让我们马上开始吧🤗

入门

什么是synchronized?

synchronized 是 Java 中的关键字,用于实现线程同步,确保多个线程在访问共享资源时不会发生冲突。它可以修饰方法或代码块,保证同一时间只有一个线程执行被修饰的代码,从而避免数据不一致问题。

为什么用synchronized?

  1. 解决竞态条件(Race Condition)

问题:多个线程同时修改同一个共享变量时,操作顺序可能被打乱,导致结果不可预测。
如果两个线程同时调用 increment(),可能发生以下情况:线程 A 读取 count=0 → 线程 B 也读取 count=0 → 两者都改为 1 → 最终 count=1(实际应为 2)。

// 有问题的count++
public class Counter {private int count = 0;public void increment() {count++; // 这行代码实际包含三步:读取值 → 修改值 → 写回值}
} 

解决:使用synchronized解决,synchronized确保同一时刻只有一个线程能执行increment(),避免值被覆盖。

public class Counter {private int count = 0;// 添加 synchronized 关键字public synchronized void increment() {count++; // 现在是一个原子操作}
}
  1. 保证内存可见性

问题:线程有自己的工作内存(缓存),修改共享变量后可能不会立即同步到主内存,导致其他线程看到旧值。

// 存在问题的flag读和写方法
public class VisibilityDemo {private boolean flag = false;public void setFlag() {flag = true; // 线程 A 修改 flag}public void checkFlag() {while (!flag); // 线程 B 可能永远看不到 flag 变为 true}
}

解决:使用synchronized解决,通过 synchronized 的锁机制,强制线程从主内存读取最新值,避免可见性问题。

public class VisibilityDemo {private boolean flag = false;// 添加 synchronized 保证可见性public synchronized void setFlag() {flag = true; // 修改后立即同步到主内存}// 同样用 synchronized 读取public synchronized boolean checkFlag() {return flag; // 从主内存读取最新值}
}
  1. 避免原子性破坏

问题:某些操作看似是“一步完成”,但实际由多个底层指令组成(如 i++),多线程环境下可能被分割执行,比如下面的转账例子。

// 非原子操作
public void transfer(Account from, Account to, int amount) {if (from.balance >= amount) {from.balance -= amount; // 非原子操作to.balance += amount;    // 可能被其他线程打断}
}

解决:使用synchronized解决。这里更安全的做法是使用全局锁(如定义一个 final Object lock),避免嵌套锁导致的死锁风险。

public void transfer(Account from, Account to, int amount) {// 锁定两个账户对象,避免并发修改synchronized (from) {synchronized (to) {if (from.balance >= amount) {from.balance -= amount;to.balance += amount;}}}
}
  1. 协调多线程的有序访问

问题:多个线程需要按特定顺序操作共享资源(如生产者-消费者模型)。

public class Queue {private List<Integer> list = new ArrayList<>();public synchronized void add(int value) {list.add(value); // 生产者线程添加数据}public synchronized int remove() {return list.remove(0); // 消费者线程移除数据}
}

解决synchronized 确保同一时刻只有一个线程操作队列,避免并发异常。

public class Queue {private List<Integer> list = new ArrayList<>();// 添加和移除方法均用 synchronized 保护public synchronized void add(int value) {list.add(value);}public synchronized int remove() {if (!list.isEmpty()) {return list.remove(0);}return -1; // 或抛异常}
}

怎么用synchronized?

我们可以看到,JLS已经规定了,可以修饰在方法和代码块中。
在这里插入图片描述

修饰方法

1.修饰实例方法
锁是当前对象实例(this),同一对象的多个线程调用该方法时会互斥。

public class Counter {private int count = 0;// 修饰实例方法:锁是当前对象实例public synchronized void increment() {count++;}
}

使用场景:多个线程操作同一个对象的实例方法时(如单例对象的资源修改)。

2.修饰静态方法
锁是类的 Class 对象(如 Counter.class),所有线程调用该类的静态方法时会互斥。

public class Counter {private static int count = 0;// 修饰静态方法:锁是 Counter.classpublic static synchronized void increment() {count++;}
}

使用场景:多线程操作静态变量(如全局计数器)。

修饰代码块

可以指定任意对象作为锁,灵活性更高。

1.锁是当前对象实例(this)

public void doSomething() {// 同步代码块:锁是当前对象实例synchronized (this) {// 需要同步的代码}
}

2.锁是类对象(Class)

public void doSomething() {// 同步代码块:锁是 Counter.classsynchronized (Counter.class) {// 需要同步的代码}
}

3.锁是任意对象

private final Object lock = new Object();public void doSomething() {// 同步代码块:锁是自定义对象synchronized (lock) {// 需要同步的代码}
}

syncrhonized在框架源码中的使用

Vector 和 Hashtable

这些类在 JDK 早期版本中通过synchronized修饰所有公共方法实现线程安全。例如Vectoradd() 方法:

public synchronized boolean add(E e) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = e;return true;
}

缺点:锁粒度粗(整个方法加锁),性能较低,现代开发中多被ConcurrentHashMapCollections.synchronizedList()替代。

StringBuffer

StringBuffer的方法均用synchronized修饰以实现线程安全,例如append()

public synchronized StringBuffer append(String str) {toStringCache = null;super.append(str);return this;
}

总结

synchronized的优点

  1. 简单易用
    • 只需在方法或代码块上添加关键字即可实现线程同步,无需手动管理锁的获取和释放。
  2. 自动释放锁
    • 当同步代码块执行完毕或发生异常时,锁会自动释放,避免死锁风险。
  3. 内置锁优化
    • JVM 对 synchronized 进行了大量优化,如锁升级机制(偏向锁 → 轻量级锁 → 重量级锁),在低竞争场景下性能较好。
  4. 内存可见性
    • 通过 synchronized 的锁机制,可以保证线程对共享变量的修改对其他线程可见(遵循 happens-before 原则)。
  5. 结构化锁
    • 锁的获取和释放必须成对出现,减少编码错误。

synchronized的缺点

  1. 性能开销
    • 在高竞争场景下,synchronized 会升级为重量级锁,导致线程阻塞和上下文切换,性能较差。
  2. 锁粒度较粗
    • 如果直接修饰方法,可能导致锁的范围过大,降低并发性能。
  3. 不可中断
    • 线程在等待锁时无法被中断(Lock 接口支持可中断的锁获取)。
  4. 功能有限
    • 不支持尝试获取锁(tryLock)、超时获取锁、公平锁等高级功能(ReentrantLock 支持)。
  5. 嵌套锁可能导致死锁
    • 如果多个线程以不同顺序获取嵌套锁,可能导致死锁。

synchronized的适用场景

  1. 低竞争场景
    • 当线程竞争不激烈时,synchronized 的性能足够好,且实现简单。
  2. 简单的线程同步需求
    • 如计数器、单例模式、简单的生产者-消费者模型等。
  3. 需要快速实现线程安全
    • 在开发初期或对性能要求不高的场景下,synchronized 是快速实现线程安全的有效工具。
  4. 需要保证内存可见性
    • 当多个线程需要共享变量时,synchronized 可以确保变量的修改对其他线程可见。
  5. 锁粒度较粗的场景
    • 如果锁的范围不需要特别精细,直接修饰方法即可满足需求。

后话

什么就结束了?别急,这个synchronized的原理,也是很有说法的。

点上关注,主播马上带你们深入学习synchronized

最近主播的下班时间都是准点,这下沉淀爽了🤗

参考

Chapter 17. Threads and Locks

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/31149.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32---FreeRTOS消息队列

一、简介 1、队列简介&#xff1a; 队列&#xff1a;是任务到任务&#xff0c;任务到中断、中断到任务数据交流的一种机制&#xff08;消息传递&#xff09;。 FreeRTOS基于队列&#xff0c;实现了多种功能&#xff0c;其中包括队列集、互斥信号量、计数型信号量、二值信号量…

目标检测Anchor-based 与 Anchor-free

一.二者对比 anchor-free和anchor-based是两种不同的目标检测方法&#xff0c;区别在于是否使用预定义的anchor框来匹配真实的目标框。 anchor-based方法使用不同大小和形状的anchor框来回归和分类目标&#xff0c;例如faster rcnn、retinanet和yolo等。anchor-free&#xff0…

Node.js与VUE安装

目录 Win下载安装 Mac下载安装 Win与Mac配置检查是否安装成功切换淘宝NPM库检查镜像配置是否生效设置 npm 全局环境目录&#xff08;避免权限问题&#xff09;WinMac VUE安装安装 Vue CLI验证 Vue CLI打开vue面板 Win 下载 直接从官网下载官网地址&#xff1a;https://nodejs…

LabVIEW基于双通道FFT共轭相乘的噪声抑制

对于双通道采集的含噪信号&#xff0c;通过FFT获取复数频谱后&#xff0c;对第二通道频谱取共轭并与第一通道频谱相乘&#xff0c;理论上可增强相关信号成分并抑制非相关噪声。此方法适用于通道间信号高度相关、噪声独立的场景&#xff08;如共模干扰抑制&#xff09;。以下为L…

c语言笔记 静态数据与ELF程序格式

数据段&#xff1a; 1.全局变量 2.常量.rodata段 3.已初始化的静态数据(全局变量).data段 4.未初始化的静态数据(static修饰的局部变量).bss段 为什么需要静态数据? 全局变量 可以在任何文件&#xff0c;函数中使用&#xff0c;数据操作上更加方便。static修饰的局部变量&a…

算法 之 树形dp 树的中心、重心

文章目录 重心实践题目小红的陡峭值 在树的算法中&#xff0c;求解树的中心和重心是一类十分重要的算法 求解树的重心 树的重心的定义&#xff1a;重心是树中的一个节点&#xff0c;如果将这个点删除后&#xff0c;剩余各个连通块中点数的最大值最小&#xff0c;那么这个节点…

Ubuntu切换lowlatency内核

文章目录 一. 前言二. 开发环境三. 具体操作 一. 前言 低延迟内核&#xff08;Lowlatency Kernel&#xff09; 旨在为需要低延迟响应的应用程序设计的内核版本。Linux-lowlatency特别适合音频处理、实时计算、游戏和其他需要及时响应的实时任务。其主要特点是优化了中断处理、调…

【Zinx】Day5-Part2:Zinx 的消息队列及多任务机制

目录 Day5-Part2&#xff1a;Zinx 的消息队列及多任务机制创建消息队列创建及启动 Worker 工作池在 Server 启动的同时对连接池进行初始化 Day5-Part2&#xff1a;Zinx 的消息队列及多任务机制 接下来我们需要给 ZInx 添加消息队列以及多任务 Worker 机制。可以通过限制 worke…

项目上传到Gitee过程

在gitee上新建一个仓库 点击“克隆/下载”获取仓库地址 电脑上要装好git 在电脑本地文件夹右键“Git Bash Here” 依次执行如下命令 git init git remote add origin https://gitee.com/qlexcel/stm32-simple.git git pull origin master git add . git commit -m ‘init’…

速算迷你世界脚本UI

--[[ --数学速算主界面 local UI"6996144362677448610" local v"6996144362677448610_" --自定义玩家数据界面 --显示界面分类 -- --称号积分幼儿园0学前班50小学生200初中生500高中生1000大学生2000研究生5000博士生10000教授50000 local A {["主屏幕…

坐落于杭州的电商代运营公司品融电商

坐落于杭州的电商代运营公司品融电商 在中国电商行业蓬勃发展的浪潮中&#xff0c;品融电商&#xff08;PINKROON&#xff09;作为一家扎根杭州的新锐品牌管理公司&#xff0c;凭借其独特的全域增长方法论和实战经验&#xff0c;迅速崛起为行业标杆。自2020年成立以来&#x…

mysql的Innodb最大支持的索引长度是多少,以及索引长度怎么计算

今天正好有空&#xff0c;来讲个之前粉丝经常问的一个知识&#xff0c;就是mysql的Innodb最大支持的索引长度是多少&#xff1f;以及索引长度怎么计算&#xff1f; 一、mysql的innodb引擎&#xff0c;创建索引最大支持的长度是多少字节&#xff1f; 不墨迹&#xff0c;直接说…

【网络安全工程】任务11:路由器配置与静态路由配置

目录 一、概念 二、路由器配置 三、配置静态路由CSDN 原创主页&#xff1a;不羁https://blog.csdn.net/2303_76492156?typeblog 一、概念 1、路由器的作用&#xff1a;通过路由表进行数据的转发。 2、交换机的作用&#xff1a;通过学习和识别 MAC 地址&#xff0c;依据 M…

Dagger 2 系列(五)——进阶之@Scope 和 @Singleton

前言&#xff1a; 在上一篇Dagger 2 系列&#xff08;四&#xff09;——Named 和 Qualifier注解介绍&#xff0c;了Named 和 Qualifier注解&#xff0c;这篇文章&#xff0c;我们将会了解另外俩个注解&#xff1a;Scope 和 Singleton。 在这篇文章中你会了解到&#xff1a; …

脑电波控制设备:基于典型相关分析(CCA)的脑机接口频率精准解码方法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、CCA的用途二、频率求解思路三、输入数据结构四、判断方法五、matlab实践1.数据集获取及处理2.matlab代码3.运行及结果 六、参考文献 前言 在脑机接口(BCI)领…

fiddler+雷电模拟器(安卓9)+https配置

一、fiddler配置 1、开启https代理 2、根证书安装&#xff1a;导出证书系统安装 二、模拟器设置 1、设置网络桥接模式 【点击安装】提示安装成功后保存即可 2、开启root&#xff08;开启adb远程调试&#xff09; 3、开启磁盘写入 4、设置WLAN代理 5、证书安装&#xff1a;物…

跨越时空的对话:图灵与GPT-4聊AI的前世今生

&#xff08;背景&#xff1a;虚拟咖啡厅&#xff0c;图灵身着1950年代西装&#xff0c;端着一杯热茶&#xff0c;GPT-4以全息投影形态坐在对面&#xff09; 图灵&#xff08;喝了口茶&#xff09;&#xff1a;“听说你能写诗&#xff1f;我当年在布莱切利园破解Enigma时&…

双击PPT文件界面灰色不可用,需要再次打开该PPT文件才能正常打开

双击PPT文件界面灰色不可用&#xff0c;需要再次打开该PPT文件才能正常打开 1. 软件环境⚙️2. 问题描述&#x1f50d;3. 解决方法&#x1f421;解决步骤 4. 结果预览&#x1f914; 1. 软件环境⚙️ Windows10 或 Windows11 专业版64位&#xff0c;安装MotionGo软件&#xff08…

【时间序列聚类】Feature-driven Time Series Clustering(特征驱动的时间序列聚类)

文章目录 1.文章介绍2.问题背景3.拟解决的问题4.主要贡献5.提出的方法5.1模型pipeline5.2特征抽取和选择5.3图渲染和社区检测5.4共现矩阵的构建5.5对共现矩阵进行聚类 6.实验6.1模型设置6.2实验结果6.3消融实验 7.结论8.个人观点9.Reference 1.文章介绍 论文出处&#xff1a;ED…

tomcat负载均衡配置

这里拿Nginx和之前做的Tomcat 多实例来实现tomcat负载均衡 1.准备多实例与nginx tomcat单机多实例部署-CSDN博客 2.配置nginx做负载均衡 upstream tomcat{ server 192.168.60.11:8081; server 192.168.60.11:8082; server 192.168.60.11:8083; } ser…