Java并发编程必备之Synchronized关键字深入解析

本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种使用方式:修饰代码块、修饰普通方法和修饰静态方法,感兴趣的朋友一起看看吧

目录
  • 一、前言
  • 二、Synchronized关键字
    • 2.1 Synchronized的特性
      • 1. 互斥
      • 2. 可重入
  • 三、synchronized 的使用示例
    • 3.1 修饰代码块: 明确指定锁哪个对象
      • 3.2直接修饰普通方法
        • 3.3 修饰静态方法
        • 四 、监视器锁(Monitor Lock)
          • 4.1 监视器锁的特点
            • 4.2 Synchronized的底层实现
              • 4.3 onitor Lock的状态
                • 4.4 Synchronized与ReentrantLock的比较
                  • 4.5 常见问题及解决方案
                    • 1. 避免过度同步
                    • 2. 避免死锁
                    • 3. 数据一致性问题
                • 五、总结

                  一、前言

                  在Java多线程编程中,线程安全是非常重要的一个概念。为了防止多个线程同时访问共享资源时出现数据不一致或其他竞态条件问题,Java提供了synchronized关键字和监视器锁(Monitor Lock)机制。这个博客将深入探讨synchronized的原理、使用场景以及与监视器锁的关系。

                  二、Synchronized关键字

                  synchronized是Java中的一个关键字,用于在多线程环境中控制对共享资源的访问。当一个线程执行synchronized修饰的方法或代码块时,其他线程将无法访问相同的资源,直到当前线程释放资源锁。

                  2.1 Synchronized的特性

                  1. 互斥

                  Synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待。

                  • 进入 synchronized 修饰的代码块, 相当于 加锁
                  • 退出 synchronized 修饰的代码块, 相当于 解锁

                     synchronized (locker){
                                  for (int i = 0; i < 5000; i++) {
                                      count++;
                                  }
                              }

                  synchronized用的锁是存在Java对象头里的。

                  可以粗略理解成, 每个对象在内存中存储的时候, 都存有一块内存表示当前的 “锁定” 状态(类似于厕所
                  的 “有人/无人”)。
                  如果当前是 “无人” 状态, 那么就可以使用, 使用时需要设为 “有人” 状态。
                  如果当前是 “有人” 状态, 那么其他人无法使用, 只能排队。

                  理解 “阻塞等待”。
                  针对每⼀把锁, 操作系统内部都维护了⼀个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝试
                  进行加锁, 就加不上了, 就会阻塞等待, ⼀直等到之前的线程解锁之后, 由操作系统唤醒⼀个新的线程,
                  再来获取到这个锁.

                  注意:

                  • 上一个线程解锁之后, 下一个线程并不是立即就能获取到锁。而是要靠操作系统来 “唤醒”. 这也就
                  是操作系统线程调度的一部分工作。
                  • 假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C
                  都在阻塞队列中排队等待.。但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能获取到锁,
                  而是和 C 重新竞争, 并不遵守先来后到的规则。
                  synchronized的底层是使用操作系统的mutex lock实现的。

                  2. 可重入

                  synchronized 同步块对同⼀条线程来说是可重入的,不会出现自己把自己锁死的问题。

                  理解 “把自己锁死”

                  ⼀个线程没有释放锁, 然后又尝试再次加锁。
                  第⼀次加锁, 加锁成功
                  lock();
                  第⼆次加锁, 锁已经被占用, 阻塞等待
                  lock();
                  按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第一次的锁被释放, 才能获取到第二
                  个锁.。但是释放第⼀个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行解锁操作. 这时候就会死锁。

                  Java 中的 synchronized可重⼊锁, 因此没有上面的问题。

                  for (int i = 0; i < 50000; i++) {
                   synchronized (locker) {
                   synchronized (locker) {
                               count++;
                             }
                        }
                  }

                  在可重入锁的内部, 包含了 “线程持有者” 和 “计数器” 两个信息。
                  • 如果某个线程加锁的时候, 发现锁已经被人占用, 但是恰好占用的正是自己, 那么仍然可以继续获取到锁, 并让计数器自增。
                  • 解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)

                  三、synchronized 的使用示例

                  synchronized 本质上要修改指定对象的 “对象头”。从使用角度来看,synchronized 也势必要搭配⼀个具体的对象来使用。

                  3.1 修饰代码块: 明确指定锁哪个对象

                  1.锁任意对象

                  public class Synchrinized1 {
                      private static int count;
                      public static void main(String[] args) {
                          Object locker=new Object();
                          Thread t1=new Thread(()->{
                              for (int i = 0; i <10000 ; i++) {
                                  synchronized (locker){
                                      count++;//锁任意对象
                                  }
                              }
                          });
                      }
                  }

                  2.锁当前对象

                  public class Synchrinized1 {
                      private static int count;
                      public static void main(String[] args) {
                          Object locker=new Object();
                          Thread t1=new Thread(()->{
                              for (int i = 0; i <10000 ; i++) {
                                  synchronized (this){
                                      count++;//锁当前对象
                                  }
                              }
                          });
                      }
                  }

                  3.2直接修饰普通方法

                  public class Synchronized2{
                      public int count=0;
                      synchronized public void methond(){
                          count++;
                      }
                  }

                  3.3 修饰静态方法

                  public class Synchronized{
                      public static int count=0;
                     public static synchronized void methon(){
                          count++;
                      }
                  }

                  四 、监视器锁(Monitor Lock)

                  Monitor Lock(监视器锁)是一种高级同步机制,底层实现了synchronized关键字的功能。每个对象都关联一个监视器锁,当线程进入synchronized块时,线程会获取该对象的监视器锁。

                  4.1 监视器锁的特点

                  • 互斥性:同一时间只有一个线程可以持有监视器锁。
                  • 可重入性:同一线程可以多次获取已持有的监视器锁。
                  • 锁升级:从未锁定状态偏向锁,再到轻量级锁,最终升级为重量级锁

                  4.2 Synchronized的底层实现

                  synchronized关键字的实现依赖于JVM,而JVM底层使用了监视器锁(Monitor Lock)来实现线程的同步。

                  当线程执行synchronized代码时,JVM会检查是否已经获得了监视器锁:

                  • 如果未加锁,当前线程会尝试获取锁。
                  • 如果已加锁,当前线程将被阻塞,直到锁被释放。

                  4.3 onitor Lock的状态

                  一个监视器锁有以下四种状态:

                  • 未锁定状态:任何线程都可以竞争锁。
                  • 偏向锁状态:只允许一个线程持有锁,减少竞争。
                  • 轻量级锁状态:多个线程竞争锁,但未发生旋转或阻塞。
                  • 重量级锁状态:线程竞争激烈,锁被线程阻塞等待。

                  4.4 Synchronized与ReentrantLock的比较

                  4.5 常见问题及解决方案

                  1. 避免过度同步

                  过度同步会降低并发性能,应尽量缩小同步范围。

                  2. 避免死锁

                  死锁是由于多个线程互相持有对方资源而导致的。可以通过如下方式避免:

                  • 按顺序加锁:对多个锁的加锁顺序进行统一。
                  • 避免嵌套锁:减少嵌套使用锁的场景。

                  3. 数据一致性问题

                  在单例模式或共享变量的场景中,必须确保所有修改共享资源的操作都在同步块内。

                  五、总结

                  通过本文,我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性。文章详细介绍了Synchronized的三种使用方式:修饰代码块、修饰普通方法和修饰静态方法。同时,我们还解析了监视器锁(Monitor Lock)的底层实现,以及SynchronizedReentrantLock的对比。最后,文章总结了并发编程中常见问题的解决方案,如避免过度同步、防止死锁和保持数据一致性。本文旨在帮助开发者更好地理解和使用Synchronized,从而编写出高效、安全的并发程序。

                  到此这篇关于Java并发编程必备之Synchronized关键字深入解析的文章就介绍到这了,更多相关Java Synchronized关键字内容请搜索骃骐网以前的文章或继续浏览下面的相关文章希望大家以后多多支持骃骐网!

                  您可能感兴趣的文章:

                  • 深入了解Java中Synchronized关键字的实现原理
                  • 一个例子带你看懂Java中synchronized关键字到底怎么用
                  • Java关键字synchronized原理与锁的状态详解
                  • Java中的synchronized关键字
                  • java中synchronized关键字的3种写法实例
                  • Java中关键字synchronized的使用方法详解
                  • Java synchronized关键字和Lock接口实现原理
                  • JAVA面试题 简谈你对synchronized关键字的理解
                  • Java中synchronized关键字引出的多种锁 问题
                  • Java中synchronized关键字修饰方法同步的用法详解

                  相关推荐:

                  Python处理JSON报JSONDecodeError怎么解决_利用try-except配合非严格模式解析

                  JSONDecodeError根本原因是输入文本违反RFC 8259规范,常见触发场景包括BOM头、末尾逗号、单引号、注释、Unicode转义不全、换行符混用;Python json模块无strict=False参数,严格解析不可绕过;推荐方案为预清洗后标准解析,次选ast.literal_eval(限可信Python字面量),或引入json5/demjson3第三方库;try-except需记录...

                  如何正确实现 Excel 读取 → 解析 → 写入的流水线式并发处理

                  本文详解如何使用 ThreadPoolExecutor 和 ProcessPoolExecutor 构建非阻塞、阶段解耦的并发流水线,避免原代码中因 as_completed 顺序等待导致的串行瓶颈,并确保 I/O 与 CPU 密集型任务各司其职、无缝衔接。 本文详解如何使用 `threadpoolexecutor` 和 `processpoolexecutor` 构建非阻塞、阶段解耦的并发流水线...

                  为什么Python训练模型时CPU占用率过高_利用Multiprocessing多进程并发

                  ProcessPoolExecutor比手写Process更稳,因其内置进程复用、超时控制、异常透传;避免AttributeError需将函数置于模块顶层且非lambda/嵌套;max_workers应据内存与任务粒度调优,而非盲目设为cpu_count()。 训练模型时 CPU 占用率“过高”不是 bug,而是 multiprocessing 正常工作的表现——它真正在压满多核,而不是像 thr...

                  为什么Python 3.11的异常处理几乎零成本_解析Zero-cost Exception

                  ExceptionTable是Python 3.11+字节码中存储异常跳转元数据的只读表,它用静态查找替代了3.10及以前依赖SETUP_FINALLY/POP_BLOCK等指令的动态块栈操作,使无异常时几乎零开销——仅保留极轻量的间接寻址,而不再执行任何异常相关字节码。 Python 3.11 的 try/except 在无异常时确实几乎不耗性能,这不是宣传话术,而是字节码层的结构性改变 —— ...

                  如何在Python中测试多线程并发安全性_利用pytest结合threading模块调试

                  pytest测多线程安全会“测不准”是因为其单线程执行机制导致测试函数返回即判定完成,后台线程可能未结束;必须用join显式同步、Event/Lock制造竞态、捕获线程异常并共享被测对象。 为什么直接用 pytest 测多线程安全会“测不准” 因为 pytest 默认是单线程执行测试函数,即使你在测试里手动启动多个 threading.Thread,它也不会自动等待所有线程结束就判定测试通过——t...

                  SimPy 容器资源 level 属性的更新时机与事件驱动调度机制解析

                  SimPy 中 Container.level 的值并非实时反映“逻辑上当前时刻”的容量状态,而是严格遵循离散事件仿真中事件触发与协程挂起/恢复的时序逻辑;其看似异常的跳变源于 yield 导致的进程切换与属性读取时机错位,需通过监听事件回调或封装访问逻辑来获取准确的状态快照。 simpy 中 `container.level` 的值并非实时反映“逻辑上当前时刻”的容量状态,而是严格遵循离散事件仿...

                  Python证书链验证失败的根源与正确OpenSSL命令解析

                  本文详解python cryptography.x509 构建多级证书链(根→中间→终端)时,openssl验证报错 unable to get issuer certificate 的真实原因——并非代码缺陷,而是证书链加载方式错误;重点阐明 -untrusted 参数的关键作用及完整验证流程。 本文详解python cryptography.x509 构建多级证书链(根→中间→终端)时,ope...

                  如何优化Scrapy爬虫的并发数量避免压垮目标服务器_动态调整CONCURRENT_REQUESTS参数

                  真正有效的做法是分层控制+动态响应:需设CONCURRENT_REQUESTS_PER_DOMAIN为第一道闸,配合AUTOTHROTTLE_ENABLED自动调优,并禁用重定向与冗余重试。 直接调低 CONCURRENT_REQUESTS 不能解决“压垮服务器”的问题,真正有效的做法是分层控制 + 动态响应,而不是靠单一参数硬调。 为什么只改 CONCURRENT_REQUESTS 是错的 这个...

                  Python 文件组织与播放列表优化:文本解析 vs 命名约定

                  本文对比两种 python 音乐播放列表管理方案——基于纯文本文件的结构化解析与基于文件名编码的隐式分组,分析其可维护性、扩展性与运行效率,并提供健壮、可复用的实现示例。 本文对比两种 python 音乐播放列表管理方案——基于纯文本文件的结构化解析与基于文件名编码的隐式分组,分析其可维护性、扩展性与运行效率,并提供健壮、可复用的实现示例。 在构建 jukebox 类应用时,如何高效、清晰地组织歌...

                  Python爬虫并发效率低怎么办_基于Asyncio与Aiohttp的异步改造

                  应改用 aiohttp + asyncio 替代 requests.get() 串行请求,因其在IO密集场景下性能高3倍以上、资源占用更低,但需合理配置连接池、超时及并发数,并避免漏写 await、混入阻塞操作、忽略异常等常见错误。 直接换 aiohttp + asyncio,别再用 requests.get() 串行发请求了。单线程协程在 IO 密集场景下,性能常比多线程高 3 倍以上,且内存和...