第六章:多线程与并发
6.4 同步与互斥
6.4.1 同步的概念与必要性
在多线程编程中,当多个线程同时访问共享资源(如变量、文件、数据库等)时,可能会导致数据不一致或程序逻辑错误。同步(Synchronization)是一种机制,用于协调多个线程对共享资源的访问,确保线程安全(Thread Safety)。
为什么需要同步?
- 竞态条件(Race Condition):多个线程同时修改共享数据时,最终结果依赖于线程执行的顺序。
- 数据不一致性:由于线程交替执行,可能导致共享数据处于不一致的状态。
- 原子性破坏:某些操作需要作为一个不可分割的单元执行,但线程切换可能导致操作被中断。
6.4.2 同步的实现方式
Java提供了多种同步机制来实现线程安全:
1. synchronized 关键字
synchronized 是Java中最基本的同步机制,可以用于方法或代码块。
同步方法:
public synchronized void increment() { count++; }锁对象是当前实例(
this),同一时间只有一个线程可以执行该方法。同步代码块:
public void increment() { synchronized (this) { count++; } }可以指定锁对象(如
this或任意对象),比同步方法更灵活。
2. ReentrantLock 类
ReentrantLock 是 java.util.concurrent.locks 包中的锁实现,提供了比 synchronized 更灵活的锁机制。
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
特点:
- 可重入:同一线程可以多次获取锁。
- 可中断:支持
lockInterruptibly()方法。 - 公平性:支持公平锁和非公平锁。
3. 原子类(AtomicInteger, AtomicLong 等)
Java提供了原子类(如 AtomicInteger),通过CAS(Compare-And-Swap)机制实现无锁线程安全。
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
6.4.3 互斥与死锁
互斥(Mutual Exclusion)
互斥是指同一时间只允许一个线程访问共享资源。Java中的锁机制(如 synchronized 和 ReentrantLock)实现了互斥。
死锁(Deadlock)
死锁是指多个线程互相持有对方需要的资源,导致所有线程都无法继续执行。
死锁的四个必要条件:
- 互斥条件:资源一次只能被一个线程占用。
- 占有并等待:线程持有资源并等待其他资源。
- 非抢占条件:线程持有的资源不能被其他线程强行抢占。
- 循环等待条件:多个线程形成环形等待关系。
避免死锁的方法:
- 避免嵌套锁:尽量减少锁的嵌套使用。
- 按固定顺序获取锁:所有线程按相同顺序获取锁。
- 使用超时机制:如
tryLock()设置超时时间。
6.4.4 线程间的通信
线程间可以通过 wait()、notify() 和 notifyAll() 方法实现协作。
wait():释放锁并进入等待状态。notify():唤醒一个等待的线程。notifyAll():唤醒所有等待的线程。
示例(生产者-消费者模型):
public class SharedBuffer {
private Queue<Integer> buffer = new LinkedList<>();
private final int CAPACITY = 5;
public synchronized void produce(int item) throws InterruptedException {
while (buffer.size() == CAPACITY) {
wait(); // 缓冲区满,等待
}
buffer.add(item);
notifyAll(); // 通知消费者
}
public synchronized int consume() throws InterruptedException {
while (buffer.isEmpty()) {
wait(); // 缓冲区空,等待
}
int item = buffer.poll();
notifyAll(); // 通知生产者
return item;
}
}
6.4.5 同步的性能考虑
同步机制虽然能保证线程安全,但可能带来性能问题:
- 锁竞争:多个线程竞争同一把锁时,会导致线程阻塞。
- 上下文切换:线程频繁切换会增加系统开销。
优化建议:
- 减少锁的粒度:尽量缩小同步代码块的范围。
- 使用读写锁(
ReadWriteLock):读操作可以并发,写操作互斥。 - 使用无锁数据结构:如
ConcurrentHashMap。
6.4.6 总结
- 同步是多线程编程中确保线程安全的核心机制。
- Java提供了
synchronized、ReentrantLock和原子类等多种同步工具。 - 避免死锁和优化同步性能是实际开发中的关键问题。
