在Java多线程编程中,当多个线程同时访问类的静态资源(如静态方法、静态变量)时,可能会引发线程安全问题,导致数据不一致或不可预期的行为,为了确保线程安全,需要对类的静态部分进行同步控制,即“锁住类”,本文将详细介绍Java中锁住类的多种实现方式、原理及注意事项。

synchronized关键字修饰静态方法:类级锁的直接实现
synchronized是Java提供的内置同步机制,当修饰静态方法时,锁的对象是当前类的Class对象,每个类在JVM中只对应一个Class实例,因此所有线程调用该静态方法时,会竞争同一个锁,从而实现线程安全。
定义一个工具类MathUtils,其静态方法add需要保证线程安全:
public class MathUtils {
public static synchronized int add(int a, int b) {
// 临界区代码
return a + b;
}
}
在此示例中,synchronized修饰静态方法add,锁对象为MathUtils.class,当线程A调用add方法时,会获取MathUtils.class的锁,其他线程必须等待线程A释放锁后才能进入该方法,这种方式简单直接,但会锁定整个方法,若方法内存在耗时操作,可能降低并发性能。
synchronized代码块锁定Class对象:细粒度的类同步控制
若仅需对方法内的部分代码进行同步,而非整个方法,可以使用synchronized代码块显式锁定类的Class对象,这种方式可以缩小同步范围,减少锁竞争,提高并发效率。
在MathUtils类中,仅对计算逻辑加锁,而参数校验部分不加锁:

public class MathUtils {
public static int add(int a, int b) {
// 非同步代码
if (a < 0 || b < 0) {
throw new IllegalArgumentException("参数不能为负数");
}
// 同步代码块
synchronized (MathUtils.class) {
// 临界区代码
return a + b;
}
}
}
与修饰静态方法相比,同步代码块更灵活,可以精确控制需要同步的代码片段,需要注意的是,锁对象必须是同一个类的Class实例,否则无法实现类级别的同步,若使用this作为锁对象,则锁定的是实例而非类,无法保证静态资源的线程安全。
使用锁对象(Lock接口):更灵活的类同步控制
除了synchronized,Java并发包(java.util.concurrent.locks)提供了Lock接口及其实现类(如ReentrantLock),支持更灵活的同步控制,与synchronized不同,Lock需要手动获取和释放锁,因此可以在获取锁时设置超时、可中断等特性,避免死锁或线程长时间阻塞。
使用ReentrantLock实现静态方法的线程安全:
import java.util.concurrent.locks.ReentrantLock;
public class MathUtils {
private static final ReentrantLock lock = new ReentrantLock();
public static int add(int a, int b) {
lock.lock(); // 获取锁
try {
// 临界区代码
return a + b;
} finally {
lock.unlock(); // 释放锁
}
}
}
ReentrantLock支持公平锁(按线程请求顺序获取锁)和非公平锁(默认,可提高吞吐量),还提供了tryLock()方法尝试获取锁,若锁不可用则立即返回,避免线程无限等待,但使用时需注意,必须在finally块中释放锁,否则可能导致锁泄漏,其他线程无法获取锁。
锁住类与锁住实例的区别:明确同步范围
在Java中,锁分为类锁和对象锁(实例锁),两者作用于不同的范围,需明确区分:

- 类锁:通过
synchronized修饰静态方法或锁定Class对象,锁对象是类的Class实例,所有线程共享同一把类锁,适用于同步静态资源。 - 对象锁:通过
synchronized修饰实例方法或锁定this对象,锁对象是类的实例,每个实例有自己的对象锁,不同实例之间互不干扰,适用于同步实例资源。
在Counter类中,静态变量count需要类锁,实例变量instanceCount需要对象锁:
public class Counter {
private static int count = 0; // 静态变量,需要类锁
private int instanceCount = 0; // 实例变量,需要对象锁
public static synchronized void incrementCount() {
count++; // 类锁同步
}
public synchronized void incrementInstanceCount() {
instanceCount++; // 对象锁同步
}
}
若误用对象锁同步静态资源,可能导致多个线程同时访问静态变量,引发线程安全问题,反之,若用类锁同步实例资源,则会降低并发性能,因为所有实例共享同一把锁。
注意事项与最佳实践
- 避免过度同步:同步会带来性能开销,应尽量缩小同步范围,仅对必要的临界区代码加锁,将耗时操作(如IO、复杂计算)移出同步块。
- 防止死锁:若多个线程互相等待对方释放锁,会导致死锁,线程A持有锁1并等待锁2,线程B持有锁2并等待锁1,可通过按固定顺序获取锁或使用
tryLock()避免死锁。 - 使用volatile配合锁:若静态变量仅被单个线程修改、多个线程读取,可使用
volatile保证可见性;若涉及读写操作,仍需使用锁保证原子性。 - 优先使用并发工具类:对于复杂的同步场景,如生产者-消费者模型,可使用
BlockingQueue、CountDownLatch等并发工具类,而非手动加锁,减少出错概率。
Java中锁住类的核心是控制对静态资源的并发访问,主要方式包括synchronized修饰静态方法、同步代码块锁定Class对象,以及使用Lock接口,选择合适的同步方式需根据场景需求平衡安全性和性能,同时注意区分类锁与对象锁、避免过度同步和死锁等问题,通过合理使用同步机制,可有效保障多线程环境下静态资源的线程安全。