Java 中的同步2025年3月23日 | 阅读 11 分钟 Java 中的同步是并发编程中的一个关键概念,它确保多个线程可以安全地与共享资源交互。简而言之,同步可以防止竞态条件,即操作的结果取决于线程执行的时序。它能够控制多个线程对任何共享资源的访问。在需要只允许一个线程访问共享资源的情况下,同步是一个更好的选择。 理解线程和共享资源线程表示程序中独立的执行路径。当多个线程并发访问共享资源时,由于操作的不可预测的交错,可能会出现问题。考虑两个线程并发递增共享变量的场景 如果两个线程同时执行 increment(),它们可能会并发读取 count 的当前值,递增它,然后写回它。这可能导致由于竞态条件而丢失更新或产生不正确的最终值。 引入同步Java 中的同步通过单个线程能够一次性独占访问与相关对象关联的同步代码块或同步方法来解决这些问题。Java 中有两种主要的同步机制:同步方法和同步块。 同步方法 在 Java 中,您可以将整个方法声明为 synchronized,这可以防止多个线程同时访问该方法。这样,同步变得更简单,因为该机制会自动应用于同步方法的所有调用。 示例:同步计数器 通过此修改,对 increment() 或 getCount() 的并发调用将是同步的,从而防止竞态条件。 同步代码块 同步块提供对共享资源的独占访问,并且一次只允许一个线程执行它。它的结构如下 这个监视器对象或锁是主题。一次只有一个线程可以持有监视器对象的锁。其他想要使用此对象进入同步块的线程必须等待,直到锁可用。 内在锁和同步在 Java 中,每个对象都自动关联一个内在锁(或监视器锁)。当一个线程进入同步块或方法时,它会获取该对象的锁,然后其他线程在锁被释放之前不允许进入该对象的同步块或方法。 死锁 一方面,同步确保排除了竞态条件,但另一方面,如果使用不当,它可能导致死锁。死锁可能导致两个或更多线程因相互等待资源而持续停滞。通过对锁进行排序并以相反的顺序释放它们来避免死锁。 锁定粒度 必须选择最佳锁定粒度,并且必须避免争用,从而有利于性能。锁定粒度过宽会降低并发性,而锁定粒度过细会导致开销增加。识别代码中唯一需要独占访问共享资源的部分并仅同步该部分。 并发集合 Java 包含 java.util.concurrent 包中常见集合类的线程安全版本,包括 ConcurrentHashMap 和 ConcurrentLinkedQueue。这些类提供内部同步机制,无需将同步控制权交给用户即可保证线程安全。 Volatile 关键字 此外,可以使用 volatile 关键字来维护线程之间变量更改的可见性。对于声明为 volatile 的变量,它们的值将始终直接从内存中读取,并且对其的写入将立即对所有其他线程可见。然而,volatile 本身并不能保证复杂指令(如递增)的原子性。 原子类 Java 在 java.util.concurrent.atomic 包中提供了原子类,包括 AtomicInteger 和 AtomicLong,它们无需使用显式同步即可提供变量的原子操作。这类类使用硬件的低级原子操作来实现线程安全。 为什么要使用同步?同步主要用于
同步类型有以下两种同步类型
在这里,我们将只讨论线程同步。 线程同步Java 中有两种线程同步类型:互斥和线程间通信。
互斥互斥有助于防止线程在共享数据时相互干扰。它可以通过以下三种方式实现
Java 中的锁概念同步围绕着一个称为锁或监视器的内部实体构建。每个对象都关联一个锁。按照惯例,需要一致访问对象字段的线程必须在访问它们之前获取对象的锁,然后在完成访问后释放锁。 从 Java 5 开始,java.util.concurrent.locks 包包含多个锁实现。 理解没有同步的问题在这个例子中,没有同步,所以输出是不一致的。让我们看一个例子 示例编译并运行输出 5 100 10 200 15 300 20 400 25 500 Java 同步方法如果您将任何方法声明为 synchronized,则它被称为同步方法。 同步方法用于锁定任何共享资源的对象。 当一个线程调用同步方法时,它会自动获取该对象的锁,并在线程完成其任务时释放它。 示例编译并运行输出 5 10 15 20 25 100 200 300 400 500 使用匿名类的同步方法示例在这个程序中,我们使用匿名类创建了两个线程,因此所需的编码量更少。 示例编译并运行输出 5 10 15 20 25 100 200 300 400 500 Java 中同步的优点线程安全:同步确保共享资源一次只被一个线程访问,从而防止竞态条件并维护数据完整性。这使得编写多线程程序更容易,而无需担心并发访问导致的不可预测行为。 一致性:通过使用同步,您可以确保对共享资源的并发操作以一致且可预测的方式执行。这对于维护程序逻辑的正确性并防止意外结果至关重要。 数据可见性:锁和内存屏障等同步机制保证一个线程对共享变量所做的更改对其他线程可见。这确保线程始终看到共享数据的最新值,从而防止因陈旧数据引起的不一致。 防止死锁:同步提供防止死锁的工具,死锁是一种两个或更多线程无限期阻塞,相互等待释放资源的情况。通过遵循最佳实践,例如以一致的顺序获取锁和使用超时,您可以最大限度地降低多线程应用程序中死锁的风险。 协调:同步通过允许线程等待满足某些条件后继续执行,从而促进线程之间的协调和通信。这使得能够实现信号量、互斥锁和屏障等同步原语,这些原语对于设计复杂的多线程算法至关重要。 高效的资源利用率:虽然同步由于锁定和上下文切换而增加了多线程程序的开销,但它实现了对 CPU 时间、内存和 I/O 设备等共享资源的高效利用。通过允许多个线程协同工作而不相互干扰,同步最大限度地提高了应用程序的吞吐量和响应能力。 与旧代码的兼容性:同步是 Java 并发中的一个基本概念,已广泛应用于库、框架和现有代码库中。通过利用同步,开发人员可以确保与依赖线程安全编程实践的旧代码和库的兼容性。 Java 中同步的缺点性能开销:同步涉及获取和释放锁,这会因上下文切换和对共享资源的争用而引入开销。这会降低性能,尤其是在许多线程争用相同锁的高并发应用程序中。 潜在的死锁:同步原语使用不当可能导致死锁,即线程无限期阻塞,相互等待释放锁。死锁很难调试,并且可能导致整个应用程序挂起,影响其可用性和可靠性。 可伸缩性降低:同步通过引入瓶颈来限制多线程应用程序的可伸缩性。当多个线程争用相同的锁时,它们可能会花费大量时间等待,从而降低整体吞吐量和可伸缩性,尤其是在具有许多 CPU 核心的系统上。 复杂性和维护:同步代码可能比单线程或无锁替代方案更复杂且更容易出错。管理锁、确保正确的锁获取和释放以及避免死锁需要仔细的设计和测试,从而增加了代码库的复杂性和维护负担。 潜在的活锁:活锁类似于死锁,但发生在线程不断响应彼此改变状态,阻止其中任何一个取得进展时。当线程以特定模式重复获取和释放锁而没有取得解决争用的进展时,可能会发生活锁。 调试困难:与同步相关的问题,例如竞态条件、死锁和活锁,调试起来可能很困难,尤其是在复杂的多线程应用程序中。这些问题可能偶尔发生,并且可能无法在受控环境中重现,从而难以诊断和修复。 并发性降低:过度使用同步可能导致并发性降低,因为线程可能会花费更多时间等待锁而不是执行有用的工作。细粒度锁定可以缓解此问题,但会增加复杂性并可能引入额外的开销。 I/O 操作可能导致性能下降:当线程在持有锁的同时阻塞 I/O 操作时,同步可能导致性能下降。这可能导致等待相同锁的其他线程不必要地阻塞,从而降低整体吞吐量和响应能力。 Java 同步 MCQ1. 与同步块相比,使用同步方法的主要优点是什么?
答案:B) 解释:同步方法提供了一种更简单的方式来确保方法一次只能由一个线程执行,从而降低了线程干扰和内存一致性错误的风险。 2. java.util.concurrent.locks.ReentrantLock 与 synchronized 关键字有何不同?
答案:C) 解释:ReentrantLock 提供了 synchronized 关键字之外的额外功能,例如中断等待锁的线程的能力,或者尝试获取锁而不会无限期阻塞的能力。 3. 如果一个持有锁的线程在由于异常退出同步块或方法时没有释放锁,会发生什么?
答案:B) 解释:在 Java 中,synchronized 关键字确保当线程退出同步块或方法时,即使是由于异常退出,锁也会自动释放。 4. 在 Java 程序中过度使用同步的潜在缺点是什么?
答案:D) 解释:过度使用同步会导致代码复杂性增加、潜在的死锁以及由于线程争用相同锁而导致的性能下降,从而对程序的效率产生负面影响。 5. 关于 Java 中的静态同步,以下哪个语句是正确的?
答案:B) 解释:静态同步方法锁定类对象本身,而不是类的实例,确保跨类的所有实例的同步方法一次只能由一个线程访问。 下一个主题Java 同步块 |
我们请求您订阅我们的新闻通讯以获取最新更新。