1. 引言
在Java多线程编程中,synchronized
关键字不仅可以用于修饰方法,还可以用于修饰代码块。与修饰方法相比,synchronized
代码块提供了更细粒度的锁控制,允许我们仅对需要同步的代码进行加锁,从而提高程序的并发性能。
2. synchronized代码块的原理
synchronized
代码块的基本语法如下
synchronized (object) {
// 需要同步的代码
}
其中,object
是锁对象,也称为监视器(monitor)。当一个线程进入synchronized
代码块时,它会先尝试获取锁对象的监视器锁。如果锁对象的监视器锁已经被其他线程持有,则该线程会被阻塞,直到锁被释放。当线程退出synchronized
代码块时,它会自动释放锁对象的监视器锁。
synchronized
代码块的优势在于,它允许我们仅对需要同步的代码进行加锁,而不是整个方法。这意味着其他不需要同步的代码可以并行执行,从而提高了程序的并发性能。
3. synchronized代码块的源码分析
虽然Java源码本身并不直接展示synchronized
代码块的实现细节,但我们可以从JVM的层面来理解其工作原理。在JVM中,synchronized
代码块的实现依赖于Java对象头中的Mark Word和Monitor对象。
- Mark Word:Java对象头中的Mark Word存储了对象的哈希码、GC分代年龄、锁状态等信息。当对象作为锁对象时,Mark Word会被用于表示锁的状态和持有锁的线程ID等信息。
- Monitor对象:Monitor是JVM内部用于实现线程同步的一个数据结构。每个Java对象都有一个与之关联的Monitor对象,用于实现线程之间的同步和通信。当线程进入
synchronized
代码块时,它会先尝试获取Monitor对象的所有权(即锁)。如果Monitor对象已经被其他线程持有,则该线程会被阻塞,直到Monitor对象被释放。
在JVM中,当线程执行到synchronized
代码块时,JVM会检查Mark Word中的锁状态。如果锁状态为无锁状态(即0),则JVM会将当前线程的ID写入Mark Word,并将锁状态设置为偏向锁(Biased Locking)或轻量级锁(Lightweight Locking)。如果锁状态不为无锁状态,则JVM会尝试获取Monitor对象的所有权。如果获取成功,则线程可以进入synchronized
代码块执行;如果获取失败,则线程会被阻塞,直到Monitor对象被释放。
4. 实战应用
4.1 实例1:线程安全的计数器
假设有一个计数器,多个线程需要同时对其进行增加操作。为了保证计数的准确性,需要使用synchronized
代码块来确保每次只有一个线程能够修改计数器的值。
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
// 使用lock作为锁对象
count++;
}
}
public int getCount() {
return count;
}
}
在这个例子中,创建了一个名为Counter
的类,其中包含一个计数器count
和一个作为锁对象的lock
。在increment
方法中,使用sync