JAVA锁机制:对象锁与类锁
在多线程编程中,合理使用锁机制是保证数据一致性和线程安全的关键。本文将通过示例详细讲解 Java 中的对象锁和类锁的原理、用法及区别。
一、未加锁的并发问题
先看一段未加锁的代码:
java
public class SynchronizedTest {
private int shareField = 0;
public void add() {
shareField++;
System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);
}
public static void main(String[] args) {
SynchronizedTest sync = new SynchronizedTest();
new Thread(() -> {
for (int n = 0; n < 100000; n++) {
sync.add();
}
}).start();
new Thread(() -> {
for (int n = 0; n < 100000; n++) {
sync.add();
}
}).start();
}
}
上述代码启动两个线程,每个线程各自循环10万次,对 shareField
进行自增。理论上,最终 shareField
应为 200000,但实际运行结果往往小于 200000:
当前线程:Thread-0 当前的shareField为:199994
当前线程:Thread-0 当前的shareField为:199995
...
原因在于多个线程并发修改同一变量,导致数据竞争。
二、对象锁
对象锁用于保护同一个实例的资源,确保同一时刻只有一个线程能访问被锁定的代码块。
1. 锁定非静态方法
java
public class SynchronizedTest {
private int shareField = 0;
public synchronized void add() {
shareField++;
System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);
}
// 省略 main 方法
}
synchronized
修饰非静态方法时,锁的是当前实例对象 (this
)。
2. 锁定 this 对象(代码块)
有时只需对方法中的部分代码加锁,可以使用同步代码块:
java
public void add() {
synchronized (this) {
shareField++;
System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);
}
}
3. 锁定特定对象
也可以指定其他对象作为锁:
java
private final Object obj = new Object();
public void add() {
synchronized (obj) {
shareField++;
System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);
}
}
对象锁特点
- 锁对象:当前实例(
this
)或指定对象 - 多线程访问同一实例的同步方法时互斥
- 不同实例之间互不影响
三、类锁
类锁用于保护类级别的资源(如静态变量),确保同一时刻只有一个线程能访问被锁定的静态资源。
1. 锁定静态方法
java
public static synchronized void add() {
shareField++;
System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);
}
synchronized
修饰静态方法时,锁的是类的 Class
对象。
2. 锁定 class 对象
java
public void add() {
synchronized (SynchronizedTest.class) {
shareField++;
System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);
}
}
3. 锁定静态实例变量
java
private static final Object obj = new Object();
public void add() {
synchronized (obj) {
shareField++;
System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);
}
}
类锁特点
- 锁对象:类的
Class
对象或静态实例变量 - 所有实例共享同一把锁,实现全局互斥
四、对象锁与类锁的区别
选择对象锁还是类锁,取决于需要保护的变量是实例级还是类级(静态)。
例如:
java
public class SynchronizedTest {
private static int shareField = 0;
public void add() {
synchronized (this) {
shareField++;
System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);
}
}
public static void main(String[] args) {
new Thread(() -> {
SynchronizedTest sync1 = new SynchronizedTest();
for (int n = 0; n < 100000; n++) {
sync1.add();
}
}).start();
new Thread(() -> {
SynchronizedTest sync2 = new SynchronizedTest();
for (int n = 0; n < 100000; n++) {
sync2.add();
}
}).start();
}
}
上述代码中,两个线程分别操作不同实例,但都修改静态变量 shareField
。此时对象锁无法保证线程安全,需使用类锁:
java
public void add() {
synchronized (SynchronizedTest.class) {
shareField++;
System.out.println("当前线程:" + Thread.currentThread().getName() + " 当前的shareField为:" + shareField);
}
}
五、总结
特性 | 对象锁 | 类锁 |
---|---|---|
锁对象 | 当前实例(this)/obj | 类的Class对象/静态实例变量 |
作用范围 | 同一实例间互斥 | 所有实例间互斥 |
适用场景 | 保护实例级变量 | 保护类级变量(静态变量) |
并发影响 | 不同实例间无互斥 | 所有实例共享同一把锁 |
实现方式 | synchronized方法/代码块 | static synchronized方法/class锁对象 |
合理选择锁的类型,是实现高效并发和线程安全的关键。