在 Java 多线程编程中,同步方法(Synchronized Method)和同步块(Synchronized Block)是用于实现线程同步的核心机制,它们的主要目的是解决多线程环境下的竞态条件(Race Condition),确保共享资源的线程安全性(一个类在多线程环境下被多个线程并发访问时,仍能确保其行为符合预期,且不会破坏共享数据的完整性)。
目录
1. 同步方法(Synchronized Method)
定义:用 synchronized
关键字修饰的方法。
作用:同一时间只允许一个线程执行该方法,其他线程必须等待锁释放。
分类:
实例方法同步:锁是当前对象实例(this
)。
public synchronized void instanceMethod() {
// 线程安全的代码
}
静态方法同步:锁是类的 Class
对象(如 MyClass.class
)。
public static synchronized void staticMethod() {
// 线程安全的代码
}
特点:
- 粗粒度锁:整个方法体被锁住,可能影响性能。
- 锁对象固定:实例方法锁
this
,静态方法锁Class
对象。
2. 同步块(Synchronized Block)
定义:用 synchronized
包裹的代码块,可指定任意对象作为锁。
作用:仅同步代码块内的逻辑,其他代码可并发执行。
语法:
synchronized (lockObject) {
// 线程安全的代码
}
特点:
- 细粒度锁:仅锁住关键代码,提升并发性能。
- 灵活锁对象:可指定任意共享对象作为锁(如
this
、自定义对象等)。 - 手动控制锁范围:避免死锁风险。
同步方法 vs 同步块
特性 | 同步方法 | 同步块 |
---|---|---|
锁范围 | 整个方法 | 代码块内的部分逻辑 |
锁对象 | 固定(this 或 Class 对象) | 可自定义任意对象 |
性能 | 较低(粗粒度) | 较高(细粒度) |
灵活性 | 低(无法选择锁对象) | 高(可精确控制锁范围) |
适用场景 | 简单同步需求 | 需要优化性能的复杂同步场景 |
关键注意事项
- 锁对象的选择:
- 必须是所有线程共享的同一对象(如
this
、静态变量等)。 - 避免使用
String
常量或基本类型包装类(可能被驻留,导致意外共享)。
- 必须是所有线程共享的同一对象(如
- 死锁风险:
- 同步块可手动控制锁范围,降低死锁概率。
- 避免嵌套锁或交叉锁。
- 性能优化:
- 优先使用同步块,仅同步必要代码。
- 减少锁的持有时间。
示例对比
同步方法:
public class Counter {
private int count = 0;
// 实例方法同步(锁是 this)
public synchronized void increment() {
count++;
}
}
同步块:
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) { // 自定义锁对象
count++;
}
}
}
总结
- 同步方法:简单但不够灵活,适合整个方法需同步的场景。
- 同步块:更灵活、性能更高,适合需要细粒度控制的场景。
- 核心原则:锁的粒度越小,并发性能越高。
讲到这里,可能有人还对线程同步和线程互斥不太清楚 ,在这里简要总结一下:
特性 | 互斥 | 同步 |
---|---|---|
核心目标 | 保证原子性,避免数据竞争 | 控制线程协作,确保执行顺序 |
实现手段 | 锁(synchronized 、显式锁) | wait() /notify() 、高级工具类 |
依赖关系 | 同步的基础(需先保证互斥) | 依赖互斥机制实现协作 |
典型场景 | 计数器自增、账户转账 | 生产者-消费者、读写锁 |
关键区别
- 互斥是同步的必要条件:同步问题必然包含互斥需求,但互斥本身不解决协作问题。
- 同步是更广义的概念:包含互斥和线程间通信(如等待、唤醒)。
同步的核心原则和策略:
- 最小化锁范围:仅同步必要的共享资源。
- 避免嵌套锁:降低死锁风险。
- 优先使用无锁结构:如
java.util.concurrent
包中的工具。 - 文档化同步策略:明确锁对象和临界区,便于维护。