Java 中的 Atomic 与 Synchronized 对比2025 年 1 月 6 日 | 阅读 5 分钟 并发编程涉及多个线程并行执行,这可以显著提高应用程序的性能。然而,管理并发执行可能会导致复杂的问题,例如竞态条件,即多个线程同时尝试修改同一变量,导致不可预测的行为。 Java 提供了多种机制来安全地处理并发。在这些机制中,`synchronized` 块和方法一直是确保互斥的传统方式,而 Java 5 中 `java.util.concurrent.atomic` 包的引入为特定用例提供了一种更有效的方法。在本节中,我们将讨论 **Atomic 类和 synchronized 方法之间的区别**,重点介绍它们的用例、性能影响和最佳实践。 Synchronized 关键字`synchronized` 关键字在 Java 中用于控制多个线程对代码关键部分的访问。它确保一次只有一个线程可以执行 `synchronized` 块或方法,从而提供了一种互斥机制。 Synchronized 如何工作?当一个线程进入 `synchronized` 块或方法时,它会获取对象或类的锁。尝试进入 `synchronized` 块或方法的其他线程将被阻塞,直到锁被释放。以下是一个 `synchronized` 方法的示例: 在上面的示例中,`increment` 和 `getCount()` 方法是 `synchronized` 的,确保一次只有一个线程可以修改或读取 `count` 变量。 性能考虑 虽然 `synchronized` 确保了线程安全,但它可能会引入显著的开销。当一个线程尝试获取锁时,如果另一个线程持有锁,它可能不得不等待。这可能导致争用,特别是在高度并发的应用程序中,其中多个线程频繁访问 `synchronized` 方法。 此外,获取和释放锁的成本可能很高。这种开销包括操作系统在管理线程调度和上下文切换上花费的时间,这可能会在争用较高的情况下降低性能。 java.util.concurrent.atomic 包`java.util.concurrent.atomic` 包提供了支持对单个变量进行无锁、线程安全操作的类。这些类,如 `AtomicInteger`、`AtomicLong` 和 `AtomicReference`,使用现代 CPU 支持的底层原子操作来确保线程安全,而无需 `synchronized`。 Atomic 类如何工作?Atomic 类利用比较并交换 (CAS) 操作来实现线程安全。CAS 是一种底层的原子指令,它仅在变量持有特定值时才更新它,从而确保原子地执行更新。以下是使用 `AtomicInteger` 的示例: 在此示例中,`increment` 方法使用 `AtomicInteger` 的 `incrementAndGet` 方法,该方法原子地递增计数,而无需显式的 `synchronized`。 性能考虑Atomic 类可以显著提高高度并发应用程序的性能。由于它们避免了获取和释放锁的开销,因此可以减少争用并提高可伸缩性。但是,原子操作仅适用于单变量更新。对于涉及多个变量的更复杂操作,可能需要传统的 `synchronized` 或其他并发机制。 Atomic 与 Synchronized 对比
高级注意事项重入性`synchronized` 方法和块是可重入的,这意味着持有锁的线程可以再次获取它而不会被阻塞。这对于递归方法或方法调用同一对象上的另一个 `synchronized` 方法特别有用。 相反,Atomic 类本身不直接支持重入。每个原子操作都是独立的,不存在持有锁的概念。 公平性`synchronized` 关键字不保证公平性。等待获取锁的线程不一定按照它们请求的顺序获得访问权。在某些情况下,这可能导致线程饥饿。相比之下,`java.util.concurrent` 包提供了显式锁(如 `ReentrantLock`),可以配置公平性策略。 死锁如果在 `synchronized` 代码中,两个或多个线程尝试以不同的顺序获取锁,则可能发生死锁。避免死锁需要仔细的设计并遵守一致的锁定顺序。Atomic 类是无锁的,因此不会发生死锁。 在 Atomic 和 Synchronized 之间进行选择单变量更新:优先选择 Atomic 类,因为它们简单且具有性能优势。它们对于计数器、标志和其他单变量状态管理特别有用。 复杂操作:对于涉及多个变量的复杂操作或 Atomic 操作不足时,请使用 `synchronized`。确保 `synchronized` 方法保持简短高效,以最大程度地减少争用。 读写锁:对于读写比例很高的场景,请考虑使用读写锁 (ReentrantReadWriteLock)。它允许多个读取者并发访问共享数据,同时仍为写入者提供独占访问。 最小化争用细粒度锁:使用更细粒度的锁来减少争用。不要同步大段代码,只同步需要独占访问的关键部分。 锁分段:将负载分散到多个锁上。例如,哈希表可以为每个桶使用一个单独的锁,从而与单个全局锁相比减少了争用。 不可变对象:尽可能优先使用不可变对象。不可变对象本质上是线程安全的,无需 `synchronized` 或 Atomic 操作。 避免常见陷阱双重检查锁定:在使用双重检查锁定 (Double-Checked Locking) 时要小心。虽然它可以提高性能,但它需要仔细实现以避免细微的错误。对于安全高效的延迟初始化,优先选择延迟初始化持有者模式 (initialization-on-demand holder idiom)。 Volatile 关键字:对于被多个线程访问但不需要完全 `synchronized` 的变量,请使用 `volatile` 关键字。Volatile 变量提供了一种轻量级的机制来确保可见性,而无需互斥。 Atomic 和 `synchronized` 机制都在 Java 的并发领域发挥着至关重要的作用。Atomic 类利用底层原子指令,为单变量操作提供了高性能且可伸缩的解决方案,从而在没有 `synchronized` 开销的情况下确保线程安全。 另一方面,`synchronized` 方法和块为涉及多个变量的复杂操作提供了通用且强大的解决方案,尽管它们的开销较高。 选择适合您需求的工具需要了解您应用程序的具体要求,并仔细权衡性能和复杂性。通过利用 Atomic 类和 `synchronized` 的优势,您可以构建高效且健壮的 Java 并发应用程序。 |
丰数(Abundant number),也称为过剩数,是一个正整数,其真因子(不包括本身)之和大于该数本身。换句话说,丰数是因子“丰富”的数。让我们探讨一下……
阅读 4 分钟
?在 Java 中,可以通过利用字符串操作和字符分类方法来分析字符串的构成,并计算不同字符类型(如大写字母、小写字母、数字和特殊字符)的百分比。本节将引导您逐步完成此过程,...
阅读 3 分钟
Java 提供了各种类和工具来管理不同的数据种类和过程。Number 类作为 Java 的数字包装类的超类,是基本类的一个示例。它包含用于转换、比较和对各种数字类型执行算术运算的方法...
阅读 6 分钟
java.text.RuleBasedCollator 类具有 getCollationElementIterator() 函数。通过 RuleBasedCollator 类获取提供的字符迭代器对象的排序元素迭代器对象。语法:public CollationElementIterator getCollationElementIterator(CharacterIterator source) 参数:字符迭代器对象可以作为参数传递给此函数。返回值:...
阅读 3 分钟
Java 长期以来一直是企业软件开发的核心,以其平台独立性、强大的生态系统和强大的社区支持而闻名。随着我们进入微服务和云计算时代,Java 仍在不断调整和变化,尤其是在引入容器化技术之后...
阅读 8 分钟
数组切片主要在 Python 和 JavaScript 等编程语言中工作,允许开发人员轻松地提取数组的特定部分。然而,由于 Java 语言的设计,数组切片的概念并不那么简单。尽管有内置的切片语法,Java 提供了...
5 分钟阅读
如何在 Java 中阻止对象被垃圾回收 我们知道,当一个对象不再被引用时,它会自动被垃圾收集器回收。在本教程中,我们将了解如何避免 Java 中对象的垃圾回收……
阅读 3 分钟
javax.naming.CompositeName 是一个类,包含一个 get() 方法。要获取此复合名称对象的组件,请使用 CompositeName 类。通过提供的位置,从复合名称对象中获取该位置上存在的组件...
阅读 2 分钟
Socket 是 Java 网络支持的核心概念。Socket 范式是在 20 世纪 80 年代初的 4.2BSD Berkeley UNIX 版本中引入的。因此,它被称为 Berkeley socket。Socket 是现代网络的基础,因为 Socket……
阅读 17 分钟
为什么非静态变量不能从静态上下文中引用? 在 Java 中,非静态变量无法从静态上下文中引用的错误通常是初学者在编译 Java 程序时遇到的。此错误发生的原因是...
5 分钟阅读
我们请求您订阅我们的新闻通讯以获取最新更新。
我们提供所有技术(如 Java 教程、Android、Java 框架)的教程和面试问题
G-13, 2nd Floor, Sec-3, Noida, UP, 201301, India