Java多线程同步是指通过特定的机制确保多个线程在访问共享资源时的有序性和安全性,防止因并发操作导致的数据不一致、竞态条件(Race Condition)等问题。其核心目标是协调线程的执行顺序,保证共享数据的完整性。
一、为什么需要同步
当多个线程同时修改共享资源时(如变量、文件、数据库等),可能出现以下问题:
- 数据不一致:多个线程同时读写导致结果不符合预期。
- 竞态条件:线程的执行顺序影响最终结果。
- 不可预知的错误:例如并发修改集合导致
ConcurrentModificationException
。
二、Java实现多线程同步的主要方式
1. synchronized
关键字
- 同步方法:锁定当前对象实例(或类的Class对象,如果是静态方法)。
public synchronized void increment() {
count++; // 临界区代码
}
- 同步代码块:更细粒度地控制锁的范围。
public void increment() {
synchronized (this) { // 显式指定锁对象
count++;
}
}
2. Lock
接口(ReentrantLock
)
提供比 synchronized
更灵活的锁机制,支持超时、公平锁等功能。
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 必须手动释放锁
}
}
3. volatile
变量
- 保证变量的可见性(任何线程修改后立即对其他线程可见)。
- 不保证原子性,适合简单的状态标志。
private volatile boolean isRunning = true;
4. 原子类(AtomicInteger
等)
- 使用CAS(Compare-And-Swap)机制保证操作的原子性。
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 无需加锁
}
5. 并发工具类
-
BlockingQueue
:线程安全的阻塞队列(如ArrayBlockingQueue
)。 -
CountDownLatch
:等待多个线程完成。 -
Semaphore
:控制同时访问资源的线程数量。 -
CyclicBarrier
:让一组线程互相等待。
三、同步的核心思想
- 互斥访问:任何时候只有一个线程能访问临界区代码。
- 可见性:线程对共享变量的修改对其他线程立即可见。
- 有序性:禁止指令重排序优化(如
volatile
或锁机制)。
示例场景:线程不安全的计数器
public class UnsafeCounter {
private int count = 0;
public void increment() {
count++; // 非原子操作(读-改-写)
}
}
当多个线程调用 increment()
时,count++
可能出现值覆盖。需要通过同步解决:
解决方案1(synchronized):
public synchronized void increment() {
count++;
}
解决方案2(ReentrantLock):
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
解决方案3(AtomicInteger):
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
四、同步的代价
- 性能损耗:锁的获取和释放需要时间。
- 死锁(Deadlock):线程互相等待对方释放锁。
- 过度同步:锁范围过大可能降低并发效率。
五、同步建议
- 尽量缩小同步代码块的范围。
- 优先使用并发工具类(如
BlockingQueue
或原子类)。 - 避免嵌套锁,防止死锁。
- 使用
volatile
或原子类替代锁(适用于简单场景)。
通过合理选择同步机制,可以在保证线程安全的前提下,最大化程序的并发性能。