Integer 类在 Java 中是线程安全的,但这需要从多个层面来理解。它的线程安全性源于其不可变性(Immutability),这是理解其并发行为的关键。


一、核心结论

Integer 对象本身是线程安全的
但包含 Integer 的容器或操作可能不安全


二、为什么 Integer 是线程安全的?

1. 不可变性(Immutability)

Integer 是一个不可变类(Immutable Class),具有以下特性:

public final class Integer extends Number implements Comparable<Integer> {
    private final int value;  // ✅ final 修饰,不可变
    
    public Integer(int value) {
        this.value = value;
    }
    
    // 只有 getter,没有 setter
    public int intValue() {
        return value;
    }
}

不可变性的优势

  • 对象创建后状态不能改变
  • 天然线程安全,无需同步
  • 可被多个线程安全共享

2. 示例:安全的共享

// 这些 Integer 对象可以被多个线程安全共享
Integer a = 42;
Integer b = Integer.valueOf(100);

// 多个线程同时读取,完全安全
new Thread(() -> System.out.println(a)).start();
new Thread(() -> System.out.println(b)).start();

三、常见的线程安全误区

虽然 Integer 对象本身安全,但使用方式可能导致线程问题。

❌ 误区 1:Integer 作为计数器

// 错误示例:试图用 Integer 做计数器
Integer counter = 0;

// 线程1
counter = counter + 1;  // 实际是:创建新 Integer 对象

// 线程2  
counter = counter + 1;

// ❌ 问题:这不是原子操作!结果可能不是 2

问题分析

  • counter + 1 会创建新的 Integer 对象
  • 赋值操作不是原子的
  • 可能发生竞态条件(Race Condition)

✅ 正确方式:使用 AtomicInteger

AtomicInteger counter = new AtomicInteger(0);

// 线程安全的递增
counter.incrementAndGet(); // 原子操作

// 或
counter.addAndGet(1);

❌ 误区 2:缓存中的 Integer

// 危险示例
private Integer cachedValue;

public Integer getValue() {
    if (cachedValue == null) {
        cachedValue = computeExpensiveValue(); // ❌ 可能被多个线程重复计算
    }
    return cachedValue;
}

问题:可能发生缓存击穿,多个线程同时进入计算。

✅ 正确方式:双重检查锁定

private volatile Integer cachedValue;

public Integer getValue() {
    Integer result = cachedValue;
    if (result == null) {
        synchronized (this) {
            result = cachedValue;
            if (result == null) {
                result = computeExpensiveValue();
                cachedValue = result;
            }
        }
    }
    return result;
}

四、Integer.valueOf() 的缓存机制

Integer.valueOf() 使用了缓存池优化,这也涉及线程安全问题。

1. 缓存范围

Integer a = Integer.valueOf(100);   // 从缓存获取
Integer b = Integer.valueOf(100);   // 同一个对象
System.out.println(a == b);         // true (在 -128~127 范围内)

Integer x = Integer.valueOf(200);   // 新对象
Integer y = Integer.valueOf(200);   // 新对象
System.out.println(x == y);         // false

2. 缓存的线程安全性

// Integer 内部缓存实现(简化)
private static class IntegerCache {
    static final Integer[] cache;
    
    static {
        cache = new Integer[256];
        for (int i = 0; i < cache.length; i++) {
            cache[i] = new Integer(i - 128);
        }
        // ✅ 静态初始化是线程安全的
    }
}

结论

  • 缓存的初始化在类加载时完成,线程安全
  • 缓存数组本身不可变,安全共享

五、装箱/拆箱的线程安全

1. 自动装箱

// 自动装箱是线程安全的
Integer a = 42;           // 等价于 Integer.valueOf(42)
Integer b = 42;

2. 自动拆箱

Integer num = 42;
int value = num;          // 等价于 num.intValue()
// intValue() 只是读取 final 字段,线程安全

六、最佳实践与建议

✅ 正确使用方式

// 1. 作为不可变值传递
public void process(Integer id) {
    // 安全读取
    System.out.println("Processing: " + id);
}

// 2. 作为 Map 的 key
Map<Integer, String> map = new HashMap<>();
map.put(42, "answer");  // Integer 作为 key 安全

// 3. 作为常量
public static final Integer MAX_RETRY = 3;

❌ 避免的使用方式

// 1. 不要用作计数器
Integer counter = 0;  // 错误

// 2. 不要试图修改内部状态(无法做到)
// Integer 没有 setter 方法

// 3. 不要用于需要原子操作的场景

✅ 替代方案

场景 推荐方案
计数器 AtomicInteger, LongAdder
累加操作 AtomicInteger, synchronized
复杂状态 synchronized 块或 ReentrantLock

七、与其他包装类的对比

包装类 是否线程安全 说明
Integer ✅ 是 不可变
Long ✅ 是 不可变
Double ✅ 是 不可变
Boolean ✅ 是 不可变
StringBuilder ❌ 否 可变,非线程安全
StringBuffer ✅ 是 可变,同步方法

八、总结

Integer 线程安全的原因:

  1. 不可变性final int value
  2. 无状态改变方法:只有 getter
  3. 缓存安全初始化:静态块保证
  4. 装箱/拆箱安全:基于不可变对象

⚠️ 使用注意事项:

  1. 对象本身安全,但操作可能不安全
  2. 不要用作可变计数器
  3. 复合操作需要额外同步
  4. 高并发场景使用 AtomicInteger

📌 一句话总结:

Integer 对象因其不可变性而天然线程安全,可安全共享;但涉及复合操作(如 i++)时,必须使用 AtomicInteger 或同步机制来保证原子性。