在 Java 并发编程中,synchronized 是最基础也是最常用的线程同步机制。作为 Java 内置的关键字,它提供了一种简单有效的方式来控制多线程对共享资源的访问。本文将全面剖析 synchronized 的工作原理、使用方式以及底层实现机制。
synchronized 主要解决多线程环境下的三个核心问题:
在实际开发中,我们经常遇到需要保证线程安全的场景。比如电商系统中的库存扣减、银行系统的账户余额操作等,都需要使用 synchronized 或其他同步机制来保证数据一致性。
同步实例方法是最简单的使用方式,通过在方法声明中添加 synchronized 关键字实现:
java复制public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
这种方式的锁对象是当前实例(this),因此:
注意:同步方法会降低并发性能,因为同一实例的所有同步方法都会互斥。如果方法之间没有共享变量,应考虑使用不同的锁对象。
静态方法的同步使用类对象作为锁:
java复制public class StaticCounter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
// 错误的同步方式
public void wrongIncrement() {
synchronized(this) { // 锁对象错误,无法保护静态变量
count++;
}
}
}
关键特点:
同步代码块提供了更灵活的同步控制:
java复制public class FineGrainedLock {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
private int value1 = 0;
private int value2 = 0;
public void updateValue1() {
synchronized(lock1) {
value1++;
}
}
public void updateValue2() {
synchronized(lock2) {
value2--;
}
}
}
优势:
在 HotSpot 虚拟机中,每个对象都有一个对象头,其中包含用于同步的 Mark Word:
code复制|-------------------------------------------------------|
| Mark Word (64 bits) |
|-------------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |
|-------------------------------------------------------|
锁状态通过最后 3 位表示:
JVM 为了优化同步性能,设计了锁升级机制:
偏向锁:适用于只有一个线程访问同步块的场景
轻量级锁:适用于线程交替执行的场景
重量级锁:适用于多线程激烈竞争的场景
每个 Java 对象都与一个 Monitor 关联,Monitor 的主要组成部分:
当线程进入 synchronized 块时:
将大锁拆分为多个小锁,减少锁竞争:
java复制// 不推荐 - 粗粒度锁
public class CoarseLock {
private final Object lock = new Object();
private int a, b, c;
public void updateAll() {
synchronized(lock) {
a++;
b++;
c++;
}
}
}
// 推荐 - 细粒度锁
public class FineLock {
private final Object lockA = new Object();
private final Object lockB = new Object();
private final Object lockC = new Object();
private int a, b, c;
public void updateA() {
synchronized(lockA) { a++; }
}
public void updateB() {
synchronized(lockB) { b++; }
}
}
JVM 会对不可能存在竞争的锁进行消除:
java复制public String concat(String s1, String s2) {
// StringBuffer 内部方法是同步的
// 但这里的 sb 是局部变量,不会被其他线程访问
// JVM 会进行锁消除优化
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
将连续的多个加锁解锁操作合并为一个:
java复制// 优化前
public void before() {
for (int i = 0; i < 100; i++) {
synchronized(lock) {
// 少量操作
}
}
}
// 优化后
public void after() {
synchronized(lock) {
for (int i = 0; i < 100; i++) {
// 合并操作
}
}
}
尽管 synchronized 使用简单,但也存在一些限制:
对于需要更复杂同步控制的场景,可以考虑使用 java.util.concurrent 包中的 Lock 接口及其实现类。
单例模式的经典实现:
java复制public class Singleton {
private volatile static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
关键点:
不同实现方式的性能比较:
java复制// 1. synchronized 方式
private int count1 = 0;
public synchronized void inc1() { count1++; }
// 2. AtomicInteger 方式
private AtomicInteger count2 = new AtomicInteger(0);
public void inc2() { count2.incrementAndGet(); }
// 3. LongAdder 方式 (高并发最优)
private LongAdder count3 = new LongAdder();
public void inc3() { count3.increment(); }
性能测试结果(100个线程,每个递增10000次):
死锁产生的四个必要条件:
避免死锁的策略:
java复制// 按固定顺序获取锁
public void transfer(Account from, Account to, int amount) {
Account first = from.id < to.id ? from : to;
Account second = from.id < to.id ? to : from;
synchronized(first) {
synchronized(second) {
// 转账操作
}
}
}
当发现系统性能下降时,可以检查:
使用 jstack 工具检测死锁:
bash复制jstack <pid>
输出示例:
code复制Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x00007f88a8003fc8 (object 0x000000076abcec58, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x00007f88a8001428 (object 0x000000076abcec68, a java.lang.Object),
which is held by "Thread-1"
使用 JOL 工具观察锁状态变化:
java复制Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
synchronized(obj) {
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
输出示例:
code复制// 无锁状态
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000)
// 轻量级锁状态
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION
0 4 (object header) f8 f1 1f 03 (11111000 11110001 00011111 00000011)
synchronized 作为 Java 并发编程的基石,理解其工作原理和最佳实践对于编写正确、高效的并发程序至关重要。随着 Java 版本的演进,虽然出现了更多高级的并发工具,但 synchronized 因其简单可靠的特点,仍然是许多场景下的首选方案。