Java中的原子类介绍

在多线程编程领域,确保数据的一致性和线程安全是开发者面临的重要挑战。Java语言提供了原子类来简化这一过程。本文将深入探讨多线程中的问题,并详细介绍Java中的原子类,包括它们的原理、优势以及使用方法。

一、Java中的原子类概述

1. 解决了什么问题

Java中的原子类主要解决了多线程环境下对共享资源的原子操作问题。例如,它们可以确保当一个线程修改变量值时,这个修改对其他所有线程立即可见,并且整个修改过程不会被其他线程打断,从而避免了数据不一致的问题。

非原子类情况下:有一个计数器类用于在多线程环境中累加一个整数值

java 复制代码
public class SimpleCounter {
    private int value = 0;
    public void increment() {
        value++; // 非原子操作
    }
    public int getValue() {
        return value;
    }
}

在多线程环境中,如果有两个线程同时调用increment()方法,可能会发生以下情况:

  1. 线程A读取value的当前值为10。
  2. 线程B也读取value的当前值为10。
  3. 线程A将value增加1,得到11,并写回内存。
  4. 线程B也将value增加1,得到11,并写回内存。

在这种情况下,两个线程都基于相同的初始值10进行了增加操作,并且都将结果11写回内存,导致最终value的值为11,而不是我们期望的12。这是因为增加操作(读取-修改-写入)不是原子的,因此两个线程的操作相互覆盖了对方的结果。

原子类情况下 :使用AtomicInteger来替代SimpleCounter

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
    private AtomicInteger value = new AtomicInteger(0);
    public void increment() {
        value.incrementAndGet(); // 原子操作
    }
    public int getValue() {
        return value.get();
    }
}

在这个例子中,如果有两个线程同时调用AtomicInteger类的incrementAndGet()方法来增加一个整数值。以下是详细的过程:

  1. 线程A的行为
    • 线程A调用incrementAndGet()方法,此时AtomicInteger的值为10。
    • 线程A开始执行CAS操作,它读取内存中的当前值,在自增后准备更新原本的值。
  2. 线程B的行为 (与此同时):
    • 在线程A开始CAS操作的同时,线程B也调用incrementAndGet()方法。
    • 由于线程A尚未完成CAS操作,线程B读取到的当前值仍然是10。
    • 线程B也准备执行CAS操作,尝试将值从10更新为11。
  3. 线程A完成CAS操作
    • 线程A的CAS操作成功,因为它读取的值是10,并且没有其他线程改变这个值,所以它将内存中的值更新为11。
  4. 线程B尝试CAS操作
    • 线程B现在尝试执行它的CAS操作,但是它会发现内存中的值已经不再是10,而是11(因为线程A的CAS操作已经完成了)。因此,线程B的CAS操作失败。
  5. 线程B的响应
    • 面对CAS操作失败,线程B不会立即放弃。它会重新读取AtomicInteger的最新值,即11。
    • 线程B再次准备执行CAS操作,这次它尝试将值从11更新为12。
  6. 可能的竞争
    • 如果此时没有其他线程干扰,线程B的CAS操作将成功,将值从11更新为12。
    • 但是,如果在这期间有其他线程(包括线程A)也尝试更新AtomicInteger的值,线程B可能需要多次重试其CAS操作,直到成功。

通过这个过程,我们可以看到,AtomicIntegerincrementAndGet()方法通过CAS机制确保了即使在多线程竞争的情况下,对共享变量value的修改也是原子性的。每个线程都在尝试更新值之前检查当前值是否未被其他线程改变,并且在尝试失败后能够自动重试,直到成功更新值。这样,即使有多个线程同时操作,value的最终值也能正确反映所有线程的增量操作。

2. 实现原理介绍

原子类的原理主要基于以下两点:

  • 硬件指令:利用处理器提供的原子指令,如比较并交换(Compare-and-Swap, CAS),来保证操作的原子性。
  • 内存屏障:通过内存屏障来保证变量的可见性和有序性,防止指令重排序。

CAS(Compare And Swap)

包含以下三个操作数:

  1. 内存位置(V):它通常是一个变量的内存地址。
  2. 预期值(A):它是指操作执行前该内存位置的值。
  3. 新值(B):它是希望更新到该内存位置的值。

工作流程如下:

  1. 它首先比较内存位置V当前的值是否与预期值A相等。
  2. 如果相等,则将内存位置V的值更新为新值B。
  3. 最后,CAS操作返回一个布尔值,表示比较并交换是否成功。

如果V的值在比较期间被其他线程改变,CAS操作将失败,并且不会更新V的值。这时,操作可以重新尝试或者采取其他措施。

内存屏障(Memory Barrier)

这是一种同步机制,用于控制特定类型的内存操作的执行顺序,并确保某些内存操作的可见性。它的主要作用包括:

  1. 可见性:确保一个线程对共享变量的修改对其他线程立即可见。这通常是通过刷新处理器缓存或无效化缓存行来实现的。
  2. 有序性:防止编译器和处理器对内存操作的指令重排序。在多线程环境中,这可以确保代码的执行顺序与程序代码中的顺序一致。

内存屏障可以分为以下几种类型:

  • 加载屏障(Load Barrier):插入在读操作之后,它确保在此屏障之前的所有读操作完成后,才能执行后续的读操作。
  • 存储屏障(Store Barrier):插入在写操作之后,它确保在此屏障之前的所有写操作都同步到主内存之后,才能执行后续的写操作。
  • 全屏障(Full Barrier):同时具备加载屏障和存储屏障的功能,它确保屏障前后的读写操作都按照特定的顺序执行。

内存屏障确保了原子类在多线程环境下对共享变量的操作是按照一定的顺序执行的,从而保证了操作的原子性、可见性和有序性。

3. 原子类的优势

  • 简化代码:原子类减少了需要开发者手动编写同步代码的需求。
  • 性能提升:相比于传统的锁机制,原子类通常提供更高的性能。

三、原子类使用方法

Java中的原子类主要位于java.util.concurrent.atomic包下,这些类提供了一种在单个变量上执行原子操作的方法,无需担心线程安全问题。以下是Java中所有的原子类及其简单示例:

1. 基本类型原子类

  • AtomicInteger:整型原子类
  • AtomicLong:长整型原子类
  • AtomicBoolean:布尔型原子类

示例:AtomicInteger

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerExample {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        
        // 比较并交换
        boolean exchanged = atomicInteger.compareAndSet(0, 1);
        System.out.println("Value after compareAndSet: " + atomicInteger.get());
        
        // 增加
        atomicInteger.addAndGet(10);
        System.out.println("Value after addAndGet: " + atomicInteger.get());
        
        // 递减
        atomicInteger.decrementAndGet();
        System.out.println("Value after decrementAndGet: " + atomicInteger.get());
    }
}

2. 数组类型原子类

  • AtomicIntegerArray:整型数组原子类
  • AtomicLongArray:长整型数组原子类
  • AtomicReferenceArray:引用类型数组原子类

示例:AtomicIntegerArray

java 复制代码
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayExample {
    public static void main(String[] args) {
        int[] array = {1, 2, 3};
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(array);
        
        // 获取和更新数组元素
        atomicIntegerArray.getAndSet(0, 4);
        System.out.println("Value at index 0: " + atomicIntegerArray.get(0));
    }
}

3. 引用类型原子类

  • AtomicReference:引用类型原子类
  • AtomicStampedReference:带有版本戳的引用类型原子类
  • AtomicMarkableReference:带有标记位的引用类型原子类

示例:AtomicReference

java 复制代码
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceExample {
    public static void main(String[] args) {
        AtomicReference<String> atomicReference = new AtomicReference<>("initialValue");
        
        // 比较并设置
        String expected = "initialValue";
        String newValue = "newValue";
        atomicReference.compareAndSet(expected, newValue);
        System.out.println("Value after compareAndSet: " + atomicReference.get());
    }
}

4. 更新器原子类

  • AtomicIntegerFieldUpdater:整数字段更新器
  • AtomicLongFieldUpdater:长整数字段更新器
  • AtomicReferenceFieldUpdater:引用字段更新器

示例:AtomicIntegerFieldUpdater

java 复制代码
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterExample {
    private static class Candidate {
        volatile int score = 0;
    }
    
    private static final AtomicIntegerFieldUpdater<Candidate> scoreUpdater =
            AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
    
    public static void main(String[] args) {
        Candidate candidate = new Candidate();
        
        // 增加分数
        scoreUpdater.addAndGet(candidate, 10);
        System.out.println("Score: " + candidate.score);
    }
}

5. 累加器原子类(Java 8+)

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

示例:LongAdder

java 复制代码
import java.util.concurrent.atomic.LongAdder;
public class LongAdderExample {
    public static void main(String[] args) {
        LongAdder adder = new LongAdder();
        
        // 累加
        adder.add(10);
        System.out.println("Value after add: " + adder.sum());
        
        // 重置
        adder.reset();
        System.out.println("Value after reset: " + adder.sum());
    }
}
相关推荐
李慕婉学姐3 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆5 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin5 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model20055 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉5 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国5 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_941882486 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈6 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_996 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹6 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理