【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.class
    public static synchronized void increment() {
        count++;
    }
}

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

修饰代码块

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

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

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

2.锁是类对象(Class)

public void doSomething() {
    // 同步代码块:锁是 Counter.class
    synchronized (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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据库的搭建

一、MySQL的安装 第一种&#xff1a; 直接下载相应的软件&#xff1a; 比如说MySQL installer、或者phpstudy第二种&#xff1a; 1.压缩包下载 下载地址&#xff1a;https://downloads.mysql.com/archives/community/ 2.解压软件包 将MySQL软件包解压在没有中文和空格的目…

React:类组件(上)

kerwin老师我来了 类组件的创建 class组件&#xff0c;js里的类命名首字符大写&#xff0c;类里面包括构造函数&#xff0c;方法 组件类要继承React.Component才有效 必须包含render方法 import React from react class App extends React.Component{render() {return <…

以教育之道御AI之术:培养未来人才的关键策略

在当今这个人工智能(AI)技术日新月异的时代,AI已经渗透到我们生活的方方面面,教育领域也不例外。然而,面对AI的浪潮,我们不仅要学会利用它来提升教学效率,更要坚守教育的本质,即“以教育之道御AI之术”,培养出能够适应未来社会需求的创新型人才。 AI技术为教育带来的…

基于qiime2的16S数据分析全流程:从导入数据到下游分析一条龙

目录 创建metadata 把数据导入qiime2 去除引物序列 双端合并 &#xff08;dada2不需要&#xff09; 质控 &#xff08;dada2不需要&#xff09; 使用deblur获得特征序列 使用dada2生成代表序列与特征表 物种鉴定 可视化物种鉴定结果 构建进化树&#xff08;ITS一般不构建进化树…

DeepSeek V3 并行训练、推理优化点(一)

训练优化1&#xff0c; FP8计算 DeepSeek-V3在训练过程中统一使用E4M3格式&#xff0c;并通过细粒度的per-tile&#xff08;1x128&#xff09;和per-group&#xff08;128x128&#xff09;量化来降低误差。 FP8的好处还体现在节省显存上&#xff08;尤其是激活值&#xff09;…

comctl32!ListView_OnSetItem函数分析LISTSUBITEM结构中的image表示图标位置

第一部分&#xff1a; BOOL ListView_SetSubItem(LV* plv, const LV_ITEM* plvi) { LISTSUBITEM lsi; BOOL fChanged FALSE; int i; int idpa; HDPA hdpa; if (plvi->mask & ~(LVIF_DI_SETITEM | LVIF_TEXT | LVIF_IMAGE | LVIF_STATE)) { …

Docker基础篇——Ubuntu下Docker安装

大家好我是木木&#xff0c;在当今快速发展的云计算与云原生时代&#xff0c;容器化技术蓬勃兴起&#xff0c;Docker 作为实现容器化的主流工具之一&#xff0c;为开发者和运维人员带来了极大的便捷 。下面我们一起进行Docker安装。 Docker的官方Ubuntu安装文档&#xff0c;如…

Python图形编程之EasyGUI: indexbox的用法

目录<<上一章&#xff1a;ynbox用法详解 下一章&#xff1a;boolbox用法详解 >> # 1 Python图形编程之EasyGUI: indexbox的用法 1.1 基本用法 indexbox提供用户一个选择不同选项的功能&#xff0c;不同的选项由按钮来表示&#xff0c;提供类似功能的还有choicebox…

大语言模型从理论到实践(第二版)-学习笔记(绪论)

大语言模型的基本概念 1.理解语言是人工智能算法获取知识的前提 2.语言模型的目标就是对自然语言的概率分布建模 3.词汇表 V 上的语言模型&#xff0c;由函数 P(w1w2 wm) 表示&#xff0c;可以形式化地构建为词序列 w1w2 wm 的概率分布&#xff0c;表示词序列 w1w2 wm…

突破极限!蓝耘通义万相2.1引爆AI多模态新纪元——性能与应用全方位革新

云边有个稻草人-CSDN博客 目录 一、 引言 二、 蓝耘通义万相2.1版本概述 三、 蓝耘通义万相2.1的核心技术改进 【多模态数据处理】 【语音识别与文本转化】 【自然语言处理&#xff08;NLP&#xff09;改进】 【跨平台兼容性】 四、 蓝耘注册 部署流程—新手也能轻松…

JVM常用概念之本地内存跟踪

问题 Java应用启动或者运行过程中报“内存不足&#xff01;”&#xff0c;我们该怎么办? 基础知识 对于一个在本地机器运行的JVM应用而言&#xff0c;需要足够的内存来存储机器代码、堆元数据、类元数据、内存分析等数据结构&#xff0c;来保证JVM应用的成功启动以及未来平…

p5.js:sound(音乐)可视化,动画显示音频高低变化

本文通过4个案例介绍了使用 p5.js 进行音乐可视化的实践&#xff0c;包括将音频振幅转化为图形、生成波形图。 承上一篇&#xff1a;vite&#xff1a;初学 p5.js demo 画圆圈 cd p5-demo copy .\node_modules\p5\lib\p5.min.js . copy .\node_modules\p5\lib\addons\p5.soun…

PDF处理控件Aspose.PDF,如何实现企业级PDF处理

PDF处理为何成为开发者的“隐形雷区”&#xff1f; “手动调整200页PDF目录耗时3天&#xff0c;扫描件文字识别错误导致数据混乱&#xff0c;跨平台渲染格式崩坏引发客户投诉……” 作为开发者&#xff0c;你是否也在为PDF处理的复杂细节消耗大量精力&#xff1f;Aspose.PDF凭…

ruo-yi项目启动备忘

ruo-yi项目启动遇到问题备忘 参考文档: 若依 手把手启动 https://blog.csdn.net/qq_43804008/article/details/132950644?utm_mediumdistribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-1-132950644-blog-137337537.235^v43^pc_blog_bottom_…

⭐LeetCode周赛 Q1. 找出最大的几近缺失整数——模拟⭐

⭐LeetCode周赛 Q1. 找出最大的几近缺失整数——模拟⭐ 示例 1&#xff1a; 输入&#xff1a;nums [3,9,2,1,7], k 3 输出&#xff1a;7 解释&#xff1a; 1 出现在两个大小为 3 的子数组中&#xff1a;[9, 2, 1]、[2, 1, 7] 2 出现在三个大小为 3 的子数组中&#xff1a;[3,…

Java 集合框架大师课:性能调优火葬场(四)

&#x1f680; Java 集合框架大师课&#xff1a;性能调优火葬场&#xff08;四&#xff09; &#x1f525; 战力值突破 95% 警告&#xff01;调优就像吃重庆火锅——要选对食材&#xff08;数据结构&#xff09;还要控制火候&#xff08;算法&#xff09;&#x1f336;️ 第一章…

【愚公系列】《Python网络爬虫从入门到精通》045-Charles的SSL证书的安装

标题详情作者简介愚公搬代码头衔华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xff0c;阿里云签约作者&#xff0c;腾讯云优秀博主&…

蓝桥杯嵌入式组第七届省赛题目解析+STM32G431RBT6实现源码

文章目录 1.题目解析1.1 分而治之&#xff0c;藕断丝连1.2 模块化思维导图1.3 模块解析1.3.1 KEY模块1.3.2 ADC模块1.3.3 IIC模块1.3.4 UART模块1.3.5 LCD模块1.3.6 LED模块1.3.7 TIM模块 2.源码3.第七届题目 前言&#xff1a;STM32G431RBT6实现嵌入式组第七届题目解析源码&…

KUKA机器人:智能制造的先锋力量

在科技日新月异的今天&#xff0c;自动化和智能化已成为推动制造业转型升级的重要引擎。作为全球领先的智能、资源节约型自动化解决方案供应商&#xff0c;KUKA机器人在这一浪潮中扮演着举足轻重的角色。本文将带您深入了解KUKA机器人的发展现状&#xff0c;探索其在智能制造领…

Ateme在云端构建可扩展视频流播平台

Akamai Connected Cloud帮助Ateme客户向全球观众分发最高质量视频内容。 “付费电视运营商和内容提供商现在可以在Akamai Connected Cloud上通过高质量视频吸引观众&#xff0c;并轻松扩展。”── Ateme首席战略官Rmi Beaudouin ​ Ateme是全球领先的视频压缩和传输解决方案提…