使用 synchronized 关键字的方法主要有两种:同步方法和同步代码块。
1. 同步方法
同步方法是最简单的使用方式。当你声明一个方法为 synchronized 时,Java 虚拟机确保在同一时刻,只有一个线程可以执行该方法。如果这个方法是一个实例方法,它锁定的是调用该方法的对象;如果是静态方法,则锁定的是这个类的 Class 对象。
public class Counter {
private int count = 0;
// 同步实例方法
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
在这个示例中,increment() 和 getCount() 都是同步的,所以它们不能被同一对象的两个线程同时访问。
public class Utils {
private static int value = 0;
// 同步静态方法
public static synchronized void addValue(int add) {
value += add;
}
}
这里的 addValue() 是一个静态同步方法,锁定的是 Utils.class 这个类对象,确保同一时间只有一个线程可以执行这个方法。
2. 同步代码块
同步代码块用于同步方法中的一部分代码,而不是整个方法。这可以减少等待时间,提高效率。同步代码块需要指定一个锁对象。
public class App {
private final Object lock = new Object();
private List<String> names = new ArrayList<>();
public void addName(String name) {
synchronized(lock) {
names.add(name);
}
}
}
在这个示例中,只有添加名称到列表的部分是同步的。通过使用锁对象,我们可以更细粒度地控制同步,而不是锁定整个方法。
使用技巧和注意事项
- 最小锁定范围:尽量只在必要的范围内使用同步,以避免性能损失。
- 避免死锁:确保多个线程不会因为相互等待对方持有的锁而陷入停滞。
- 选择合适的锁对象:在使用同步代码块时,选择适当的对象作为锁,避免使用字符串常量或全局对象作为锁,以减少意外的锁冲突。
实例方法上的synchronized:
- 当一个实例方法被synchronized修饰时,线程在调用该方法之前,需要获得当前实例对象的锁(即this)。
- 如果一个线程已经持有该对象的锁,其他线程将无法调用任何被synchronized修饰的实例方法,直到该锁被释放。
- 锁定的范围是具体的实例,即不同实例的synchronized实例方法可以并发执行,因为它们锁定的是不同的对象。
public synchronized void instanceMethod() {
// method implementation
}
静态方法上的synchronized:
- 当一个静态方法被synchronized修饰时,线程在调用该方法之前,需要获得该类的Class对象的锁(即ClassName.class)。
- 如果一个线程已经持有该类的Class对象的锁,其他线程将无法调用任何被synchronized修饰的静态方法,直到该锁被释放。
- 锁定的范围是整个类,即无论多少实例存在,所有实例共享同一个Class对象的锁。
public static synchronized void staticMethod() {
// method implementation
}
区别总结
锁定对象的范围:
- 实例方法锁定的是调用该方法的具体实例对象。不同实例之间互不影响。
- 静态方法锁定的是整个类的Class对象,即所有实例共享这一个锁。
并发控制粒度:
- 实例方法提供的是对象级别的锁,粒度较细,可以允许多个实例并行执行各自的synchronized实例方法。
- 静态方法提供的是类级别的锁,粒度较粗,只要有一个线程在执行被锁定的静态方法,其他线程无论是访问同一个静态方法还是其他被synchronized修饰的静态方法,都将被阻塞。
实例方法锁定示例:
public class MyClass {
public synchronized void instanceMethod() {
// 仅锁定当前实例对象
}
}
MyClass obj1 = new MyClass();
MyClass obj2 = new MyClass();
Thread t1 = new Thread(() -> obj1.instanceMethod());
Thread t2 = new Thread(() -> obj2.instanceMethod());
t1.start();
t2.start(); // t1和t2可以并行执行,因为锁定的是不同的对象
静态方法锁定示例:
public class MyClass {
public static synchronized void staticMethod() {
// 锁定整个类的Class对象
}
}
Thread t1 = new Thread(() -> MyClass.staticMethod());
Thread t2 = new Thread(() -> MyClass.staticMethod());
t1.start();
t2.start(); // t1和t2无法并行执行,因为锁定的是同一个Class对象