synchronized 关键字锁定的对象是什么?请解释其含义。
参考回答
在 Java 中,synchronized
关键字是用于实现线程同步的,它通过锁定对象来控制多个线程对共享资源的访问。锁定的对象决定了线程同步的粒度,也决定了哪些线程可以进入临界区。
锁定对象的核心概念
- 锁定的对象是同步代码块或方法中指定的对象。
-
只有持有该对象锁的线程可以执行同步代码,其它线程必须等待,直到锁被释放。
-
锁的范围:
- 静态方法锁住的是类的 Class 对象。
- 非静态方法锁住的是当前实例对象。
- 同步代码块锁住的是指定的对象。
synchronized 的使用方式与锁定对象
1. 同步实例方法
当 synchronized
修饰一个实例方法时,锁定的是当前实例对象 (this
)。
示例:
public class SynchronizedExample {
public synchronized void methodA() {
System.out.println(Thread.currentThread().getName() + " is executing methodA");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodB() {
System.out.println(Thread.currentThread().getName() + " is executing methodB");
}
}
public class Main {
public static void main(String[] args) {
SynchronizedExample example = new SynchronizedExample();
Thread t1 = new Thread(() -> example.methodA(), "Thread-1");
Thread t2 = new Thread(() -> example.methodB(), "Thread-2");
t1.start();
t2.start();
}
}
解释:
methodA
和methodB
都是同步实例方法,锁定的是同一个对象example
。- 两个线程会互相等待,只有一个线程可以进入同步方法。
2. 同步静态方法
当 synchronized
修饰静态方法时,锁定的是类的 Class 对象。
示例:
public class SynchronizedExample {
public static synchronized void staticMethodA() {
System.out.println(Thread.currentThread().getName() + " is executing staticMethodA");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static synchronized void staticMethodB() {
System.out.println(Thread.currentThread().getName() + " is executing staticMethodB");
}
}
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(SynchronizedExample::staticMethodA, "Thread-1");
Thread t2 = new Thread(SynchronizedExample::staticMethodB, "Thread-2");
t1.start();
t2.start();
}
}
解释:
staticMethodA
和staticMethodB
都是静态方法,锁定的是SynchronizedExample.class
。- 无论多少实例对象,这些方法都会互相排斥。
3. 同步代码块
使用 synchronized
关键字锁定指定对象,灵活控制锁的范围。
示例:
public class SynchronizedExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void methodA() {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + " is executing methodA");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void methodB() {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + " is executing methodB");
}
}
}
public class Main {
public static void main(String[] args) {
SynchronizedExample example = new SynchronizedExample();
Thread t1 = new Thread(() -> example.methodA(), "Thread-1");
Thread t2 = new Thread(() -> example.methodB(), "Thread-2");
t1.start();
t2.start();
}
}
解释:
methodA
锁定lock1
,methodB
锁定lock2
。- 两个方法锁定不同对象,因此线程
t1
和t2
可以并发执行。
锁定对象的意义
1. 锁定对象决定同步的粒度
- 锁的粒度越小,并发性能越高,但代码逻辑可能更复杂。
- 锁的粒度越大,并发性能会降低,但代码更简单。
2. 锁定对象的唯一性
- 如果两个线程锁定的是同一个对象,则会互斥。
- 如果两个线程锁定的是不同对象,则可以并发执行。
3. 锁的类型
- 实例锁:锁住当前实例,影响该对象的同步方法。
- 类锁:锁住类的
Class
对象,影响所有同步静态方法。
注意事项与建议
- 避免锁粒度过大
- 如果锁定的范围太大,会严重降低并发性能。
- 应尽量缩小锁的范围,例如使用同步代码块锁定关键部分。
- 保证锁对象的唯一性
- 锁对象应该是所有线程共享的,通常使用类的字段或
this
。 - 不要使用局部变量作为锁对象,因为局部变量在线程间不可共享。
- 锁对象应该是所有线程共享的,通常使用类的字段或
- 慎用类锁
- 类锁会影响类的所有线程,适合需要全局同步的操作。
- 如果只需要实例级别的同步,应该使用实例锁。
- 防止死锁
- 确保锁的获取顺序一致,避免循环等待。
总结
- synchronized锁定的对象由同步代码的修饰对象决定:
- 实例方法:锁定当前实例(
this
)。 - 静态方法:锁定类的
Class
对象。 - 同步代码块:锁定代码块中指定的对象。
- 实例方法:锁定当前实例(
- 使用时需根据具体场景选择合适的锁定对象和范围,确保线程安全的同时提高并发性能。