Java并发基础:原子类之AtomicMarkableReference全面解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: AtomicMarkableReference类能够确保引用和布尔标记的原子性更新,有效避免了多线程环境下的竞态条件,其提供的方法可以轻松地实现基于条件的原子性操作,提高了程序的并发安全性和可靠性。

Java并发基础:原子类之AtomicMarkableReference全面解析 - 程序员古德

内容概要

AtomicMarkableReference类能够确保引用和布尔标记的原子性更新,有效避免了多线程环境下的竞态条件,其提供的方法可以轻松地实现基于条件的原子性操作,提高了程序的并发安全性和可靠性。

核心概念

场景举例

AtomicMarkableReference 类将一个布尔标记与一个引用关联起来,可以原子性地更新这对值,因此在多线程环境中,可以确保标记和引用的更新是作为一个不可分割的操作发生的,不会出现中间状态被其他线程观察到的情况。

模拟一个业务场景,假如,有一个在线购物平台,其中有一个非常重要的业务是库存管理,在这个系统中,每个商品都有一个库存数量,当库存数量为零时,商品就不能再被购买了,但是,由于系统是高并发的,可能会有多个线程(可能是多个用户同时发起购买请求)同时尝试减少同一个商品的库存。

在这种情况下,可以使用AtomicMarkableReference来解决这个问题,可以将库存数量封装在一个对象中,并使用 AtomicMarkableReference 来引用这个对象,标记位可以用来表示这个库存数量是否已经被其他线程“锁定”以减少。

当一个线程尝试减少库存时,它首先会检查标记位,如果标记位表示库存没有被锁定,那么该线程会尝试原子性地设置标记位为“锁定”状态,并获取当前的库存数量,如果成功,该线程就可以安全地减少库存,而不用担心其他线程同时修改它,减少库存后,该线程会原子性地清除标记位,表示库存现在可以被其他线程锁定和修改了。

如果在尝试锁定库存时失败(因为其他线程已经锁定了它),那么该线程可以选择重试、等待或者返回一个错误消息给用户,通过使用 AtomicMarkableReference,可以确保在高并发环境下库存数量的正确性和一致性,避免出现超卖的情况。

其它场景

AtomicMarkableReference 类主要用于解决在并发环境下的数据一致性问题,特别是当需要原子性地更新一个引用及其关联的状态(通常由一个布尔标记表示),通常可以用来解决以下类似的问题:

  1. 无锁数据结构:在实现无锁(lock-free)或基于乐观锁(optimistic locking)的数据结构时,AtomicMarkableReference 可以用来原子性地更新节点的引用和状态。
  2. 状态跟踪:当需要跟踪某个对象的状态时,可以使用 AtomicMarkableReference。例如,可能有一个对象需要在多个线程之间共享,并且需要知道这个对象是否已经被处理过,通过将对象和一个标记(表示处理状态)存储在 AtomicMarkableReference 中,可以确保在检查和处理对象时操作的原子性。
  3. 缓存一致性:在构建缓存系统时,AtomicMarkableReference 可以用来确保缓存条目的原子性更新,例如,当缓存条目需要被无效或更新时,可以使用标记来表示条目的有效性,并使用 AtomicMarkableReference 来原子性地更新条目和标记。
  4. 避免ABA问题:在使用基于比较并交换(Compare-and-Swap, CAS)操作的原子类时,可能会遇到所谓的ABA问题,这是指一个变量原来的值是A,被另一个线程改成B后又改回A,那么使用CAS检查这个变量的线程就会发现它的值没有发生变化,而实际上它已经被其他线程修改过了。AtomicMarkableReference 通过引入一个额外的标记位来避免这个问题,因为即使引用的值没有变化,标记位的变化也可以被用来检测中间的状态变化。

核心案例

下面是一个简单的Java代码,演示了如何使用 AtomicMarkableReference 类:

import java.util.concurrent.atomic.AtomicMarkableReference;  

public class AtomicMarkableReferenceDemo {
   
     

    public static void main(String[] args) {
   
     
        // 创建一个 AtomicMarkableReference 实例,初始值为 "Hello" 和标记 false  
        AtomicMarkableReference<String> atomicRef = new AtomicMarkableReference<>("Hello", false);  

        // 客户端调用:尝试更新引用和标记  
        boolean updated = atomicRef.compareAndSet("Hello", "World", false, true);  
        System.out.println("Updated: " + updated); // 输出:Updated: true  

        // 获取当前值和标记  
        String currentValue = atomicRef.getReference();  
        boolean currentMark = atomicRef.isMarked();  
        System.out.println("Current Value: " + currentValue + ", Mark: " + currentMark); // 输出:Current Value: World, Mark: true  

        // 尝试基于旧值和旧标记更新,但这次会失败,因为当前值或标记与预期的不匹配  
        updated = atomicRef.compareAndSet("Hello", "Java", false, false);  
        System.out.println("Updated: " + updated); // 输出:Updated: false  

        // 再次获取当前值和标记,以确认没有变化  
        currentValue = atomicRef.getReference();  
        currentMark = atomicRef.isMarked();  
        System.out.println("Current Value: " + currentValue + ", Mark: " + currentMark); // 输出:Current Value: World, Mark: true  
    }  
}

在上面代码中,先创建了一个 AtomicMarkableReference 实例,初始值为字符串 "Hello" 和标记 false,使用 compareAndSet 方法尝试原子地更新这个引用和标记,如果当前值等于 "Hello" 且标记为 false,则更新为 "World" 和 true,这个方法返回一个布尔值,表示是否成功更新了引用和标记,

之后,再次使用 compareAndSet 方法尝试基于不同的旧值和旧标记进行更新,但这次更新会失败,因为当前的值或标记与预期的不匹配,最后,,再次获取并打印当前的值和标记,以确认它们没有因为失败的 compareAndSet 调用而改变。

技术原理

实现原理

AtomicMarkableReference的实现原理主要依赖于底层的硬件支持,特别是原子性的比较并交换(Compare-and-Swap, CAS)操作。CAS操作是一种无锁算法,它允许多个线程在没有使用锁的情况下安全地操作共享数据。

AtomicMarkableReference中,CAS操作被用来确保引用和标记的同时更新是原子的,具体来说,AtomicMarkableReference内部维护了两个字段:一个是引用的对象,另一个是一个布尔标记,这两个字段的更新都是原子性的,这是通过CAS操作来实现的。

但是,AtomicMarkableReference并没有使用一个单一的CAS操作来同时更新引用和标记,实际上,它使用了两个分离的CAS操作,并且这两个操作之间需要通过某种方式来确保原子性,这通常是通过在内部使用一个循环来实现的,循环会一直进行,直到引用和标记都成功更新,或者确定更新不可能成功为止。

为了确保原子性,AtomicMarkableReference采用了一种称为“双CAS”或“双重检查锁定”的技术,这种技术涉及到两个步骤:首先检查引用和标记的当前值是否与期望值匹配,如果匹配,则执行CAS操作来尝试更新它们,如果在这个过程中,任何一个值发生了变化(由于其他线程的并发修改),那么更新就会失败,需要重新尝试。

底层算法

底层算法的核心是两个CAS操作:一个用于更新引用,另一个用于更新标记,这两个操作都需要满足一定的条件才能成功。当尝试更新引用时,需要确保当前的标记值与期望的标记值相匹配;同样地,当尝试更新标记时,也需要确保当前的引用值与期望的引用值相匹配。

但是,这两个CAS操作并不是完全独立的,它们以一种方式组合在一起,以确保在引用和标记的更新之间不会发生其他线程的干扰,这通常是通过在内部使用一个循环来实现的,该循环会一直尝试更新引用和标记,直到成功为止或者确定无法成功为止(例如,由于其他线程的并发修改导致条件不再满足)。

总结:AtomicMarkableReference类通过利用底层的CAS操作以及一种称为“双CAS”或“双重检查锁定”的技术来实现在并发环境中对引用和标记的原子性更新。但是需要注意的,并不是所有的方法都是原子的(例如set方法),在使用时需要特别小心以确保线程安全。

核心API

以下是 AtomicMarkableReference 类中主要方法的含义:

  1. AtomicMarkableReference(V initialRef, boolean initialMark)
    • 构造函数,用于创建一个新的 AtomicMarkableReference 实例,设置初始引用值和标记。
  2. V getReference()
    • 获取当前引用的对象。
  3. boolean isMarked()
    • 获取当前标记的值。
  4. void set(V newReference, boolean newMark)
    • 设置新的引用值和标记,注意,这个方法不是原子的;它首先设置引用,然后设置标记,如果需要原子性,应使用 compareAndSet 方法。
  5. boolean weakCompareAndSet(V expectReference, V newReference, boolean expectedMark, boolean newMark)
    • 以原子方式设置引用和标记的值,但允许更大的并发性,如果当前引用等于 expectReference 且当前标记等于 expectedMark,则更新为 newReferencenewMark,此方法可能更频繁地失败,并需要循环重试,但它对系统其他部分的干扰更小。
  6. boolean compareAndSet(V expectReference, V newReference, boolean expectedMark, boolean newMark)
    • 以原子方式设置引用和标记的值,如果当前引用等于 expectReference 且当前标记等于 expectedMark,则更新为 newReferencenewMark,此方法提供了强一致性保证。
  7. boolean attemptMark(V expectedReference, boolean newMark)
    • 如果当前引用等于 expectedReference,则以原子方式设置标记为 newMark

这些方法提供了对带有标记的引用的原子操作,可以在多线程环境中安全地使用,compareAndSet 方法是此类中最常用的方法之一,因为它提供了一种以线程安全方式更新引用的方法,只有当引用和标记的当前值与预期值匹配时才会进行更新。

注意: set 方法可以设置新的引用和标记,但它并不提供原子性保证,如果需要在设置引用和标记时保持原子性,应该使用 compareAndSetweakCompareAndSet 方法。

核心总结

Java并发基础:原子类之AtomicMarkableReference全面解析 - 程序员古德

AtomicMarkableReference类结合了原子引用和一个布尔标记,为并发编程带来了便利,优点在于,它能在多线程环境下保证引用和标记的原子性更新,有效避免了竞态条件,提高了程序的并发安全性,它的使用相对简单直观,通过compareAndSet等方法可以很容易地实现基于旧值的条件更新。

单相比于普通的原子引用,它占用的空间更多,因为它需要额外存储一个标记,在高并发场景下,由于需要同时更新引用和标记,可能会带来一定的性能开销。

当需要在并发环境中维护一个引用,并且需要根据某个条件(由标记表示)来原子地更新该引用时,推荐使用AtomicMarkableReference,但在对性能要求极高或对内存占用敏感的场景中,可能需要权衡其带来的开销和收益,考虑使用其他同步机制或数据结构。

关注我,每天学习互联网编程技术 - 程序员古德

END!
END!
END!

相关文章
|
7天前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
55 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
14天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
12天前
|
Java 数据库连接 Spring
反射-----浅解析(Java)
在java中,我们可以通过反射机制,知道任何一个类的成员变量(成员属性)和成员方法,也可以堆任何一个对象,调用这个对象的任何属性和方法,更进一步我们还可以修改部分信息和。
|
1月前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
1月前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
2天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
31 17
|
12天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
14天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
14天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
14天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
41 3