在 Java 中,线程安全(Thread Safety)是指当多个线程同时访问某个类、对象或方法时,其行为始终符合预期,且不会出现数据不一致或逻辑错误。线程安全的核心是解决多线程环境下的 竞态条件(Race Condition) 和 数据可见性 问题。
1. 线程不安全的表现
示例:线程不安全的计数器
public class UnsafeCounter {
private int count = 0;
public void increment() {
count++; // 非原子操作:实际是 read -> modify -> write
}
public int getCount() {
return count;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
UnsafeCounter counter = new UnsafeCounter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final count: " + counter.getCount());
// 预期 2000,实际可能小于 2000(如 1998)
}
}原因:count++ 是非原子操作,多个线程可能同时读取旧值并覆盖写入。
2. 实现线程安全的 6 种方法
方法 1:使用synchronized同步
通过同步代码块或方法,保证同一时间只有一个线程访问共享资源。
public class SafeCounter {
private int count = 0;
public synchronized void increment() { // 同步方法
count++;
}
public int getCount() {
return count;
}
}方法 2:使用ReentrantLock
显式锁提供更灵活的同步控制(如尝试锁、超时锁)。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SafeCounter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}方法 3:使用原子类(Atomic Classes)
Java 提供
java.util.concurrent.atomic 包下的原子类(如 AtomicInteger),基于 CAS(Compare-And-Swap)实现无锁线程安全。
import java.util.concurrent.atomic.AtomicInteger;
public class SafeCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}方法 4:使用不可变对象(Immutable Objects)
不可变对象天然线程安全,因为其状态在创建后不可修改。
public final class ImmutableUser {
private final String name;
private final int age;
public ImmutableUser(String name, int age) {
this.name = name;
this.age = age;
}
// 没有 setter 方法
public String getName() { return name; }
public int getAge() { return age; }
}方法 5:使用线程局部变量(ThreadLocal)
每个线程拥有独立的变量副本,避免共享。
public class ThreadLocalDemo {
private static ThreadLocal<Integer> threadLocalCount = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
threadLocalCount.set(threadLocalCount.get() + 1);
}
System.out.println(Thread.currentThread().getName() + ": " + threadLocalCount.get());
};
new Thread(task).start();
new Thread(task).start();
}
}方法 6:使用并发容器(Concurrent Collections)
Java 提供线程安全的容器类,如 ConcurrentHashMap、CopyOnWriteArrayList。
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentMapDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 0);
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
map.compute("key", (k, v) -> v + 1); // 原子操作
}
};
Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("Final value: " + map.get("key")); // 2000
}
}3. 线程安全的级别
- 1. 不可变(Immutable):对象状态不可修改(如 String)。
- 2. 绝对线程安全:所有操作线程安全(如 AtomicInteger)。
- 3. 相对线程安全:单个操作线程安全,但复合操作需外部同步(如 Vector)。
- 4. 线程兼容:需通过同步机制保证安全(如 ArrayList)。
4. 线程安全的注意事项
- 1. 避免共享变量:优先使用局部变量或线程封闭(如 ThreadLocal)。
- 2. 减少同步范围:同步代码块应尽量小,避免性能问题。
- 3. 注意锁的粒度:粗粒度锁可能降低并发性能。
- 4. 避免死锁:按顺序获取锁,或使用超时机制。
- 5. 使用并发工具类:优先选择 java.util.concurrent 包下的工具。
5. 常见线程不安全类及替代方案
非线程安全类线程安全替代方案
ArrayListCopyOnWriteArrayListHashMapConcurrentHashMapSimpleDateFormatDateTimeFormatter(Java 8+)StringBuilderStringBuffer(同步方法)
通过合理选择同步策略和工具,可以高效解决 Java 多线程编程中的线程安全问题。
