在多线程环境下 i++
不是线程安全的,主要原因是因为 i++
不是原子性的。该操作实际上是由三步组成:
- 读取变量
i
的当前值 - 执行
i + 1
操作 - 将新值写回变量
i
假如有两个线程都执行 i++ 的操作,他们可能会发生如下情况:
- 线程 A 读取变量
i
的当前值,比如是 5。 - 线程 A 计算
i + 1
,得到 6。 - 在线程 A 完成写回
i
之前,线程 B 也读取变量i
的当前值(仍然是 5,因为线程 A 尚未更新它)。 - 线程 A 将其计算结果 6 写回变量
i
。 - 线程 B 也将其计算结果(也是 6,因为它是基于
i
原来的值 5 计算的)写回变量i
。
i++
执行被执行了两次,理论上值应该等于 7,但事实其值等于 6。为了防止这种情况,我们需要保证 i++
的操作是原子性的,目前有三种方法来实现:
- 使用 synchronized
- 使用 ReentrantLock
- 使用 AtomicInteger
使用 synchronized
将 i++
操作放在 synchronized 代码块中,可以确保同一时间只有一个线程在执行这个操作。
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
使用 ReentrantLock
ReentrantLock 采取了和 synchronized 一样的锁机制,保证同一时间只有一个线程在执行这个操作。相比 synchronized ,ReentrantLock 提供了更加灵活的锁机制。
public class Counter {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
// 获取锁
lock.lock();
try {
count++;
} finally {
// 释放锁
lock.unlock();
}
}
}
使用 AtomicInteger
AtomicInteger
是一个提供原子操作的 int
值的线程安全类。它允许我们在多线程环境中执行诸如递增、递减、加法、减法等操作,而无需使用 synchronized
或其他锁机制。AtomicInteger
是通过使用底层的非阻塞硬件原语(如 CAS - Compare-And-Swap)来实现线程安全的。
public class Counter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
}
最后
使用 volatile 是无法保证 i++ 线程安全的,所以回答时千万不要答 volatile,我在面试的时候如果朋友有人回答使用 volatile,我会返过来再深究你一波 volatile、synchronized、Lock、CAS,如果你对并发不熟悉的话会死得很惨。