选择 synchronized 的作用范围时,核心原则是尽量缩小锁的粒度以最大化并发度。具体做法是:优先使用实例方法锁(锁当前对象)或代码块锁(锁特定资源),避免使用静态方法锁(锁整个类)。如果多个线程只访问不同的实例字段,应分别锁定各自的实例;如果涉及共享状态,则需确保所有访问该共享状态的代码都受同一把锁保护。

实例方法与静态方法的锁对象区别是什么
实例方法锁和静态方法锁的根本区别在于它们锁定的对象不同。实例方法锁锁定的是调用该方法的对象实例(即 this),而静态方法锁锁定的是该类对应的 Class 对象。这意味着,一个线程持有实例方法锁时,其他线程仍然可以访问同一个对象的静态同步方法,也可以访问其他实例的同步实例方法,但无法访问同一个实例的其他同步实例方法。反之,持有静态方法锁时,会阻塞所有对该类的静态同步方法的访问,无论哪个实例调用。

public class LockScopeExample {
// 实例同步方法:锁住 this
public synchronized void instanceMethod() {
System.out.println("Instance method locked on: " + this);
}
// 静态同步方法:锁住 Class 对象
public static synchronized void staticMethod() {
System.out.println("Static method locked on: " + LockScopeExample.class);
}
public static void main(String[] args) {
LockScopeExample obj1 = new LockScopeExample();
LockScopeExample obj2 = new LockScopeExample();
Thread t1 = new Thread(() -> obj1.instanceMethod());
Thread t2 = new Thread(() -> obj2.instanceMethod());
Thread t3 = new Thread(() -> LockScopeExample.staticMethod());
t1.start();
t2.start();
t3.start();
}
}运行上述代码,t1 和 t2 可以同时执行,因为它们锁定的是不同的实例对象。而 t3 的执行不受 t1 或 t2 的影响,因为它锁定的是类对象。但如果再启动一个线程调用 obj1.staticMethod(),它将与 t3 竞争类锁,从而产生阻塞。
立即学习“Java免费学习笔记(深入)”;
代码块锁如何精确控制锁的范围
使用 synchronized 代码块可以更精细地控制锁的范围,只锁定需要保护的共享资源,而不是整个方法。这种方式允许在锁释放前执行非临界区的代码,从而提高并发性能。关键是要选择一个合适的锁对象,通常是一个私有的、不可变的对象引用,以避免外部干扰。

public class BlockLockExample {
private final Object lock = new Object();
private int count = 0;
public void increment() {
// 只锁定共享资源 count 的访问
synchronized (lock) {
count++;
}
// 这里不持有锁,可以执行其他非临界区操作
System.out.println("Non-critical work done.");
}
public int getCount() {
synchronized (lock) {
return count;
}
}
public static void main(String[] args) {
BlockLockExample example = new BlockLockExample();
Thread t1 = new Thread(() -> example.increment());
Thread t2 = new Thread(() -> example.increment());
t1.start();
t2.start();
}
}在上述代码中,increment 方法只在修改 count 变量时持有锁,而在打印日志时不持有锁。这使得其他线程可以在 t1 或 t2 执行非临界区代码时,同时进入另一个线程的非临界区,提高了并发度。锁对象 lock 被声明为 private final,确保其不会被外部修改,保证了锁的安全性。
当多个线程访问不同实例字段时如何优化锁
当多个线程访问不同实例字段时,可以通过将锁细化到每个字段或使用独立的锁对象来优化并发性能。例如,如果一个类有多个独立的计数器,可以为每个计数器分配一个独立的锁对象,这样线程在更新一个计数器时不会阻塞其他线程更新另一个计数器。这种策略称为细粒度锁或分段锁。
public class FineGrainedLockExample {
private final Object counter1Lock = new Object();
private final Object counter2Lock = new Object();
private int counter1 = 0;
private int counter2 = 0;
public void incrementCounter1() {
synchronized (counter1Lock) {
counter1++;
}
}
public void incrementCounter2() {
synchronized (counter2Lock) {
counter2++;
}
}
public int getCounter1() {
synchronized (counter1Lock) {
return counter1;
}
}
public int getCounter2() {
synchronized (counter2Lock) {
return counter2;
}
}
public static void main(String[] args) {
FineGrainedLockExample example = new FineGrainedLockExample();
Thread t1 = new Thread(() -> example.incrementCounter1());
Thread t2 = new Thread(() -> example.incrementCounter2());
t1.start();
t2.start();
}
}在上述代码中,incrementCounter1 和 incrementCounter2 方法分别使用不同的锁对象。因此,t1 和 t2 可以同时执行,互不阻塞。这种细粒度的锁策略显著提高了并发性能,特别是在高负载场景下。需要注意的是,如果某个操作需要同时访问多个字段,则需要获取多个锁,这可能会增加死锁的风险,需谨慎处理。


















