跳转到内容

Java线程安全

来自代码酷


简介[编辑 | 编辑源代码]

线程安全是Java多线程编程中的核心概念,指当多个线程同时访问某个类、对象或方法时,系统能确保其行为始终符合设计预期,不会因线程调度顺序而产生数据不一致或逻辑错误。线程安全的本质是通过同步机制控制对共享资源的并发访问。

为什么需要线程安全[编辑 | 编辑源代码]

当多个线程访问共享数据时,可能引发以下问题:

  • 竞态条件(Race Condition):线程执行顺序影响最终结果
  • 内存可见性(Memory Visibility):线程修改对其他线程不可见
  • 指令重排序(Instruction Reordering):编译器/处理器优化导致意外行为

graph TD A[线程A读取x=1] --> B[线程B读取x=1] B --> C[线程A写入x=2] C --> D[线程B写入x=2] D --> E[最终x=2而非预期3]

线程安全实现方式[编辑 | 编辑源代码]

1. 不可变对象[编辑 | 编辑源代码]

最简单的方式是使用不可变(Immutable)对象:

public final class ImmutableValue {
    private final int value;
    
    public ImmutableValue(int value) {
        this.value = value;
    }
    
    public int getValue() {
        return value;
    }
}

2. 同步方法[编辑 | 编辑源代码]

使用synchronized关键字修饰方法:

public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

3. 同步代码块[编辑 | 编辑源代码]

更细粒度的控制:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

4. volatile 关键字[编辑 | 编辑源代码]

解决可见性问题:

public class SharedObject {
    private volatile int sharedCounter = 0;
}

5. 原子类[编辑 | 编辑源代码]

java.util.concurrent.atomic包提供的原子类:

AtomicInteger atomicInt = new AtomicInteger(0);

int newValue = atomicInt.incrementAndGet(); // 原子操作

6. 线程安全集合[编辑 | 编辑源代码]

Java并发包提供的线程安全集合:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);

线程安全级别[编辑 | 编辑源代码]

根据安全程度可分为:

安全级别 描述 示例
不可变 对象完全不可变 String
无条件安全 内部同步保证安全 ConcurrentHashMap
有条件安全 需要正确使用才能安全 Collections.synchronizedList
非线程安全 完全不保证安全 ArrayList

死锁与避免[编辑 | 编辑源代码]

四个必要条件: 1. 互斥条件 2. 请求与保持 3. 不可剥夺 4. 循环等待

避免策略:

  • 加锁顺序一致
  • 使用定时锁(tryLock
  • 死锁检测

graph LR A[线程1: 锁A] --> B[线程1: 请求锁B] C[线程2: 锁B] --> D[线程2: 请求锁A]

性能考量[编辑 | 编辑源代码]

同步会带来性能开销,需权衡:

  • 减少同步范围
  • 使用读写锁(ReentrantReadWriteLock
  • 考虑无锁编程

实际案例[编辑 | 编辑源代码]

银行转账问题[编辑 | 编辑源代码]

public class BankAccount {
    private double balance;
    
    public synchronized void deposit(double amount) {
        balance += amount;
    }
    
    public synchronized void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
        }
    }
    
    // 必须同步整个转账过程
    public static void transfer(BankAccount from, 
                               BankAccount to, 
                               double amount) {
        synchronized(from) {
            synchronized(to) {
                from.withdraw(amount);
                to.deposit(amount);
            }
        }
    }
}

常见误区[编辑 | 编辑源代码]

  • 认为volatile能解决所有同步问题
  • 过度同步导致性能下降
  • 忽略复合操作的非原子性
  • 误用线程安全集合的迭代器

数学原理[编辑 | 编辑源代码]

线程安全本质是维护不变式(Invariants)。对于共享变量x,其不变式可以表示为:

t1,t2T,if t1 writes x before t2 reads x, then t2 sees t1's write

其中T是所有线程的集合。

最佳实践[编辑 | 编辑源代码]

  1. 优先使用不可变对象
  2. 缩小同步范围
  3. 文档记录线程安全策略
  4. 使用高级并发工具(ExecutorService, CountDownLatch等)
  5. 避免在同步块中调用外部方法

总结[编辑 | 编辑源代码]

Java线程安全是多线程编程的基础,理解不同实现方式的适用场景和性能影响至关重要。开发者应根据具体需求选择合适的同步策略,并通过测试验证线程安全性。