Java 线程安全详解
1. 线程安全概述
1.1 什么是线程安全
线程安全是指当多个线程访问同一个对象时,如果不需要额外的同步,调用这个对象的行为都能获得正确的结果,那么这个对象就是线程安全的。
线程安全的三个特性:
- 原子性(Atomicity):操作不可被中断,要么全部执行,要么全部不执行
- 可见性(Visibility):一个线程对共享变量的修改,其他线程能够立即看到
- 有序性(Ordering):程序执行的顺序按照代码的先后顺序执行
1.2 线程安全问题的根源
线程安全问题主要来源于:
- 共享数据:多个线程访问同一个数据
- 并发访问:多个线程同时访问
- 非原子操作:操作可以被中断
- 内存可见性:线程间数据同步问题
2. 线程安全问题的常见场景
2.1 竞态条件(Race Condition)
public class Counter {
private int count = 0;
// 非线程安全的方法
public void increment() {
count++; // 这个操作不是原子的
}
public int getCount() {
return count;
}
}
问题分析:
count++实际上包含三个操作:读取、计算、写入- 多个线程同时执行时可能导致数据丢失
2.2 内存可见性问题
public class VisibilityProblem {
private boolean flag = false;
public void setFlag() {
flag = true; // 线程A设置
}
public void checkFlag() {
while (!flag) {
// 线程B可能永远看不到flag的变化
}
}
}
2.3 指令重排序问题
public class ReorderingExample {
private int a = 0;
private int b = 0;
// 线程A
public void methodA() {
a = 1;
b = 1;
}
// 线程B
public void methodB() {
if (b == 1) {
// 这里a可能还是0,因为指令重排序
System.out.println("a = " + a);
}
}
}
3. 线程安全的解决方案
3.1 synchronized 关键字
3.1.1 同步方法
public class SynchronizedCounter {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
3.1.2 同步代码块
public class SynchronizedBlock {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
3.1.3 静态同步方法
public class StaticSynchronized {
private static int count = 0;
// 静态同步方法,锁的是Class对象
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
3.2 volatile 关键字
public class VolatileExample {
private volatile boolean flag = false;
private volatile int count = 0;
public void setFlag() {
flag = true; // 保证可见性
}
public void checkFlag() {
while (!flag) {
// 能够看到flag的变化
}
}
// 注意:volatile不能保证复合操作的原子性
public void increment() {
count++; // 仍然不是线程安全的
}
}
3.3 Lock 接口
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class LockExample {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
// 使用ReentrantLock
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
// 使用读写锁
public void write() {
rwLock.writeLock().lock();
try {
count++;
} finally {
rwLock.writeLock().unlock();
}
}
public int read() {
rwLock.readLock().lock();
try {
return count;
} finally {
rwLock.readLock().unlock();
}
}
}
3.4 原子类(Atomic Classes)
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicExample {
private AtomicInteger count = new AtomicInteger(0);
private AtomicBoolean flag = new AtomicBoolean(false);
private AtomicReference<String> data = new AtomicReference<>("initial");
public void increment() {
count.incrementAndGet(); // 原子操作
}
public void setFlag() {
flag.set(true);
}
public void updateData(String newData) {
data.set(newData);
}
// CAS操作
public boolean compareAndSet(int expected, int update) {
return count.compareAndSet(expected, update);
}
}
3.5 ThreadLocal
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public void increment() {
threadLocal.set(threadLocal.get() + 1);
}
public int getValue() {
return threadLocal.get();
}
// 使用完后要清理,避免内存泄漏
public void cleanup() {
threadLocal.remove();
}
}
4. 线程安全的集合类
4.1 同步集合
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SynchronizedCollections {
private List<String> list = Collections.synchronizedList(new ArrayList<>());
private Set<String> set = Collections.synchronizedSet(new HashSet<>());
private Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
public void addToList(String item) {
list.add(item); // 线程安全
}
public void iterateList() {
// 需要额外的同步
synchronized (list) {
for (String item : list) {
System.out.println(item);
}
}
}
}
4.2 并发集合
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ConcurrentCollections {
private ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
private CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
private ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
public void addToMap(String key, String value) {
map.put(key, value); // 线程安全
}
public void addToList(String item) {
list.add(item); // 线程安全
}
public void addToQueue(String item) {
queue.offer(item); // 线程安全
}
}
5. 线程安全的最佳实践
5.1 设计原则
- 避免共享状态:尽量使用不可变对象
- 最小化同步范围:只同步必要的代码
- 使用线程安全的类:优先使用并发包中的类
- 避免死锁:注意锁的顺序
5.2 不可变对象
// 不可变类,天然线程安全
public final class ImmutablePoint {
private final int x;
private final int y;
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
public ImmutablePoint move(int dx, int dy) {
return new ImmutablePoint(x + dx, y + dy);
}
}
5.3 避免死锁
public class DeadlockPrevention {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
// 错误的做法 - 可能导致死锁
public void method1() {
synchronized (lock1) {
synchronized (lock2) {
// 业务逻辑
}
}
}
public void method2() {
synchronized (lock2) {
synchronized (lock1) {
// 业务逻辑
}
}
}
// 正确的做法 - 按顺序获取锁
public void safeMethod1() {
synchronized (lock1) {
synchronized (lock2) {
// 业务逻辑
}
}
}
public void safeMethod2() {
synchronized (lock1) { // 同样的顺序
synchronized (lock2) {
// 业务逻辑
}
}
}
}
5.4 双重检查锁定模式
public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
6. 性能考虑
6.1 锁的粒度
public class LockGranularity {
private final Object lock = new Object();
private int count1 = 0;
private int count2 = 0;
// 粗粒度锁 - 性能较差
public void coarseGrainedLock() {
synchronized (lock) {
count1++;
count2++;
// 其他操作...
}
}
// 细粒度锁 - 性能较好
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void fineGrainedLock() {
synchronized (lock1) {
count1++;
}
synchronized (lock2) {
count2++;
}
}
}
6.2 无锁编程
import java.util.concurrent.atomic.AtomicInteger;
public class LockFreeProgramming {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
int current;
do {
current = count.get();
} while (!count.compareAndSet(current, current + 1));
}
public int getCount() {
return count.get();
}
}
7. 测试线程安全
7.1 多线程测试
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadSafetyTest {
public static void testCounter() throws InterruptedException {
Counter counter = new Counter();
int threadCount = 1000;
int operationsPerThread = 1000;
CountDownLatch latch = new CountDownLatch(threadCount);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
for (int i = 0; i < threadCount; i++) {
executor.submit(() -> {
try {
for (int j = 0; j < operationsPerThread; j++) {
counter.increment();
}
} finally {
latch.countDown();
}
});
}
latch.await();
executor.shutdown();
int expected = threadCount * operationsPerThread;
int actual = counter.getCount();
System.out.println("Expected: " + expected);
System.out.println("Actual: " + actual);
System.out.println("Thread safe: " + (expected == actual));
}
}
7.2 压力测试
public class StressTest {
public static void stressTest() throws InterruptedException {
for (int i = 0; i < 100; i++) {
testCounter();
Thread.sleep(10);
}
}
}
8. 常见陷阱和注意事项
8.1 同步方法中的非同步调用
public class SynchronizationPitfall {
private int count = 0;
public synchronized void increment() {
count++;
// 调用非同步方法可能导致问题
doSomethingElse();
}
private void doSomethingElse() {
// 这个方法不是同步的,可能导致线程安全问题
if (count > 100) {
// 处理逻辑
}
}
}
8.2 锁对象的选择
public class LockObjectChoice {
private int count = 0;
// 错误:使用String作为锁对象
private final String lock = "lock";
// 正确:使用Object作为锁对象
private final Object lockObject = new Object();
public void increment() {
synchronized (lockObject) { // 不要使用String
count++;
}
}
}
8.3 避免在构造函数中启动线程
public class ConstructorThreadPitfall {
private int count = 0;
public ConstructorThreadPitfall() {
// 错误:在构造函数中启动线程
new Thread(() -> {
count++; // 可能导致问题
}).start();
}
}
9. 总结
线程安全是 Java 并发编程的核心概念,理解并正确应用线程安全机制对于编写高质量的多线程程序至关重要。
关键要点:
- 理解原子性、可见性、有序性三个特性
- 选择合适的同步机制(synchronized、volatile、Lock、原子类)
- 使用线程安全的集合类
- 遵循最佳实践,避免常见陷阱
- 进行充分的测试验证
推荐学习路径:
- 掌握基本的同步机制
- 学习并发包中的高级工具
- 实践无锁编程
- 深入理解 JVM 内存模型
- 学习性能优化技巧
