第8章

⚛️ 原子类

掌握Java原子类的使用、CAS算法原理以及ABA问题的解决方案

学习目标

Atomic包概述

Java的java.util.concurrent.atomic包提供了一系列原子类,这些类可以在多线程环境下进行原子操作,无需使用synchronized关键字或其他锁机制。原子类基于CAS(Compare-And-Swap)算法实现,提供了高效的无锁编程解决方案。

核心理解

原子类的核心优势在于提供了无锁的线程安全操作,避免了传统锁机制可能带来的性能开销和死锁风险。

原子类的分类

基本类型原子类
AtomicInteger、AtomicLong、AtomicBoolean等,用于基本数据类型的原子操作。
引用类型原子类
AtomicReference、AtomicStampedReference、AtomicMarkableReference等。
数组类型原子类
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray等。
字段更新器
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater等,用于更新对象的字段。

AtomicInteger详解

AtomicInteger是最常用的原子类之一,提供了对int类型变量的原子操作。它内部使用volatile关键字保证可见性,使用CAS算法保证原子性。

基本使用示例

AtomicInteger基本操作
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDemo {
    private static AtomicInteger counter = new AtomicInteger(0);
    
    public static void main(String[] args) throws InterruptedException {
        // 创建多个线程进行计数
        Thread[] threads = new Thread[10];
        
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter.incrementAndGet(); // 原子递增
                }
            });
            threads[i].start();
        }
        
        // 等待所有线程完成
        for (Thread thread : threads) {
            thread.join();
        }
        
        System.out.println("最终计数值: " + counter.get()); // 输出: 10000
    }
}

常用方法

AtomicInteger主要方法
  • get():获取当前值
  • set(int newValue):设置新值
  • getAndSet(int newValue):设置新值并返回旧值
  • compareAndSet(int expect, int update):CAS操作
  • incrementAndGet():先递增再获取
  • getAndIncrement():先获取再递增
  • addAndGet(int delta):先加法再获取
  • getAndAdd(int delta):先获取再加法

AtomicReference详解

AtomicReference提供了对引用类型的原子操作,可以原子地更新对象引用。这在实现无锁数据结构时非常有用。

使用示例

AtomicReference示例
import java.util.concurrent.atomic.AtomicReference;

class User {
    private String name;
    private int age;
    
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

public class AtomicReferenceDemo {
    private static AtomicReference userRef = 
        new AtomicReference<>(new User("张三", 25));
    
    public static void main(String[] args) {
        User oldUser = userRef.get();
        User newUser = new User("李四", 30);
        
        // 原子地更新用户引用
        boolean success = userRef.compareAndSet(oldUser, newUser);
        
        if (success) {
            System.out.println("更新成功: " + userRef.get());
        } else {
            System.out.println("更新失败");
        }
    }
}

CAS算法原理

CAS(Compare-And-Swap)是一种无锁算法,它包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。当且仅当V的值等于A时,CAS才会将V的值设为B,否则不会执行任何操作。

CAS操作流程

1. 比较内存位置V的值与预期值A
2. 如果相等,将内存位置V的值更新为新值B
3. 如果不相等,不做任何操作
4. 返回操作是否成功

CAS的优缺点

优点
  • 无锁操作,避免线程阻塞
  • 性能高,减少上下文切换
  • 避免死锁问题
缺点
  • ABA问题
  • 循环时间长开销大
  • 只能保证一个共享变量的原子操作

ABA问题及解决方案

ABA问题是CAS算法的一个经典问题:如果一个值原来是A,变成了B,又变回了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

ABA问题示例

ABA问题演示
import java.util.concurrent.atomic.AtomicReference;

public class ABADemo {
    private static AtomicReference atomicRef = 
        new AtomicReference<>(100);
    
    public static void main(String[] args) throws InterruptedException {
        // 线程1:模拟ABA操作
        Thread t1 = new Thread(() -> {
            int value = atomicRef.get();
            System.out.println("线程1读取值: " + value);
            
            // 模拟一些处理时间
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // 尝试CAS操作
            boolean success = atomicRef.compareAndSet(value, value + 1);
            System.out.println("线程1 CAS操作: " + success);
        });
        
        // 线程2:制造ABA场景
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // A -> B
            atomicRef.compareAndSet(100, 200);
            System.out.println("线程2将值改为200");
            
            // B -> A
            atomicRef.compareAndSet(200, 100);
            System.out.println("线程2将值改回100");
        });
        
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        
        System.out.println("最终值: " + atomicRef.get());
    }
}

使用AtomicStampedReference解决ABA问题

AtomicStampedReference通过引入版本号(时间戳)来解决ABA问题。每次更新时不仅比较值,还比较版本号。

AtomicStampedReference解决ABA问题
import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicStampedReferenceDemo {
    private static AtomicStampedReference stampedRef = 
        new AtomicStampedReference<>(100, 1);
    
    public static void main(String[] args) throws InterruptedException {
        // 线程1
        Thread t1 = new Thread(() -> {
            int[] stamp = new int[1];
            int value = stampedRef.get(stamp);
            int currentStamp = stamp[0];
            
            System.out.println("线程1读取值: " + value + ", 版本号: " + currentStamp);
            
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            // 尝试CAS操作,版本号也要匹配
            boolean success = stampedRef.compareAndSet(
                value, value + 1, currentStamp, currentStamp + 1);
            System.out.println("线程1 CAS操作: " + success);
        });
        
        // 线程2:制造ABA场景
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            int[] stamp = new int[1];
            int value = stampedRef.get(stamp);
            
            // A -> B
            stampedRef.compareAndSet(value, 200, stamp[0], stamp[0] + 1);
            System.out.println("线程2将值改为200,版本号: " + (stamp[0] + 1));
            
            // 获取最新状态
            value = stampedRef.get(stamp);
            
            // B -> A
            stampedRef.compareAndSet(value, 100, stamp[0], stamp[0] + 1);
            System.out.println("线程2将值改回100,版本号: " + (stamp[0] + 1));
        });
        
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        
        int[] finalStamp = new int[1];
        int finalValue = stampedRef.get(finalStamp);
        System.out.println("最终值: " + finalValue + ", 版本号: " + finalStamp[0]);
    }
}

实战案例:无锁计数器

下面我们实现一个高性能的无锁计数器,对比synchronized和原子类的性能差异。

无锁计数器实现
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.CountDownLatch;

public class CounterComparison {
    private static final int THREAD_COUNT = 10;
    private static final int INCREMENT_COUNT = 100000;
    
    // 使用synchronized的计数器
    static class SynchronizedCounter {
        private long count = 0;
        
        public synchronized void increment() {
            count++;
        }
        
        public synchronized long getCount() {
            return count;
        }
    }
    
    // 使用AtomicLong的计数器
    static class AtomicCounter {
        private AtomicLong count = new AtomicLong(0);
        
        public void increment() {
            count.incrementAndGet();
        }
        
        public long getCount() {
            return count.get();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        // 测试synchronized计数器
        testSynchronizedCounter();
        
        // 测试原子计数器
        testAtomicCounter();
    }
    
    private static void testSynchronizedCounter() throws InterruptedException {
        SynchronizedCounter counter = new SynchronizedCounter();
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < INCREMENT_COUNT; j++) {
                    counter.increment();
                }
                latch.countDown();
            }).start();
        }
        
        latch.await();
        long endTime = System.currentTimeMillis();
        
        System.out.println("Synchronized计数器:");
        System.out.println("最终计数: " + counter.getCount());
        System.out.println("耗时: " + (endTime - startTime) + "ms\n");
    }
    
    private static void testAtomicCounter() throws InterruptedException {
        AtomicCounter counter = new AtomicCounter();
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < INCREMENT_COUNT; j++) {
                    counter.increment();
                }
                latch.countDown();
            }).start();
        }
        
        latch.await();
        long endTime = System.currentTimeMillis();
        
        System.out.println("Atomic计数器:");
        System.out.println("最终计数: " + counter.getCount());
        System.out.println("耗时: " + (endTime - startTime) + "ms");
    }
}
性能对比结果

在高并发场景下,原子类通常比synchronized具有更好的性能表现,特别是在竞争激烈的情况下。原子类避免了线程阻塞和上下文切换的开销。

最佳实践

选择合适的原子类
根据数据类型选择对应的原子类,避免不必要的装箱拆箱操作。
注意ABA问题
在需要检测值变化历史的场景中,使用AtomicStampedReference。
权衡性能
在低竞争场景下,原子类和synchronized性能差异不大,选择更易理解的方案。
避免复杂逻辑
原子类适合简单操作,复杂业务逻辑仍建议使用锁机制。
上一章:Condition条件 返回目录 下一章:线程池详解