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
线程安全的原因:
- 不可变性:
final int value
- 无状态改变方法:只有 getter
- 缓存安全初始化:静态块保证
- 装箱/拆箱安全:基于不可变对象
⚠️ 使用注意事项:
- 对象本身安全,但操作可能不安全
- 不要用作可变计数器
- 复合操作需要额外同步
- 高并发场景使用
AtomicInteger
📌 一句话总结:
Integer
对象因其不可变性而天然线程安全,可安全共享;但涉及复合操作(如i++
)时,必须使用AtomicInteger
或同步机制来保证原子性。