1. Synchronized 基础
1.1 Synchronized 的使用方式
1.1.1 修饰实例方法
java
public class SynchronizedMethod {
// 修饰实例方法,锁是当前实例对象(this)
public synchronized void instanceMethod() {
// 临界区代码
System.out.println("实例方法锁");
}
}
1.1.2 修饰静态方法
java
public class SynchronizedStatic {
// 修饰静态方法,锁是当前类的Class对象
public static synchronized void staticMethod() {
// 临界区代码
System.out.println("静态方法锁");
}
}
1.1.3 修饰代码块
java
public class SynchronizedBlock {
private final Object lock = new Object();
public void method() {
// 对象锁
synchronized (this) {
// 临界区代码
}
// 类锁
synchronized (SynchronizedBlock.class) {
// 临界区代码
}
// 自定义对象锁
synchronized (lock) {
// 临界区代码
}
}
}
2. Synchronized 实现原理
2.1 对象头结构
在 JVM 中,对象在内存中的布局分为三部分:
-
对象头 (Header)
-
实例数据 (Instance Data)
-
对齐填充 (Padding)
对象头包含以下信息:
32位 JVM 对象头布局
java
|--------------------------------------------------------------|
| Mark Word (32 bits) |
|--------------------------------------------------------------|
| HashCode (25 bits) | Age (4 bits) | Biased (1 bit) | Normal (01) |
|--------------------------------------------------------------|
| ThreadID (23 bits) | Epoch (2 bits) | Age (4 bits) | Biased (1 bit) | Biased (01) |
|--------------------------------------------------------------|
| Lock Pointer (30 bits) | Heavyweight (00) |
|--------------------------------------------------------------|
| Unused (30 bits) | GC (11) |
|--------------------------------------------------------------|
64位 JVM 对象头布局
java
|------------------------------------------------------------------------------|
| Mark Word (64 bits) |
|------------------------------------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | Normal
|------------------------------------------------------------------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | Biased
|------------------------------------------------------------------------------|
| ptr_to_lock_record:62 | lock:2 | Lightweight Locked
|------------------------------------------------------------------------------|
| ptr_to_heavyweight_monitor:62 | lock:2 | Heavyweight Locked
|------------------------------------------------------------------------------|
| | lock:2 | GC
|------------------------------------------------------------------------------|
2.2 Monitor 机制
java
public class MonitorMechanism {
/**
* 每个Java对象都与一个Monitor关联
* Monitor包含以下组件:
* 1. Owner:当前持有锁的线程
* 2. EntryList:等待获取锁的线程队列
* 3. WaitSet:调用了wait()的线程队列
*/
public synchronized void method() {
// 编译后的字节码包含以下指令:
// monitorenter - 尝试获取Monitor所有权
// 临界区代码
// monitorexit - 释放Monitor
}
}
2.3 字节码分析
java
public class BytecodeAnalysis {
public synchronized void syncMethod() {
System.out.println("synchronized method");
}
public void syncBlock() {
synchronized (this) {
System.out.println("synchronized block");
}
}
}
// 编译后的字节码示例:
/*
syncMethod: // 方法级的同步在常量池中标记
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
syncBlock:
monitorenter // 获取锁
... // 代码块内容
monitorexit // 释放锁
*/
3. JDK 版本优化历程
3.1 JDK 1.0 - 1.5:重量级锁时代
java
public class HeavyweightLockJDK15 {
/**
* JDK 1.5及之前:Synchronized是完全的重量级锁
* 缺点:
* 1. 性能差:需要操作系统内核态切换
* 2. 无法中断:线程一直阻塞
* 3. 非公平锁
*
* 工作流程:
* 1. 线程请求锁
* 2. 系统调用,从用户态切换到内核态
* 3. 操作系统管理线程阻塞和唤醒
* 4. 再次切换回用户态
*/
public static void demonstrate() {
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获取锁");
try {
Thread.sleep(3000); // 模拟长时间持有
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程2获取锁");
}
});
t1.start();
t2.start();
// t2会一直阻塞,无法中断
}
}
3.2 JDK 1.6:锁优化革命
3.2.1 偏向锁(Biased Locking)
java
public class BiasedLockingJDK16 {
/**
* 偏向锁优化:
* 1. 针对只有一个线程访问同步块的场景
* 2. 消除无竞争情况下的同步开销
* 3. 延迟到有竞争时才升级
*
* 启用参数:-XX:+UseBiasedLocking(JDK 15之前默认开启)
*/
public static void testBiasedLock() {
Object obj = new Object();
// 第一次获取锁,JVM会启用偏向锁
synchronized (obj) {
System.out.println("第一次加锁,启用偏向锁");
}
// 同一线程再次获取锁,直接进入(无需CAS)
synchronized (obj) {
System.out.println("同一线程再次加锁,直接进入");
}
// 另一个线程尝试获取锁,触发偏向锁撤销
new Thread(() -> {
synchronized (obj) {
System.out.println("其他线程加锁,撤销偏向锁升级为轻量级锁");
}
}).start();
}
/**
* 偏向锁工作流程:
* 1. 检查对象头中的偏向锁标志和锁标志位
* 2. 如果可偏向(01),检查线程ID是否指向当前线程
* 3. 是:直接进入同步块
* 4. 否:尝试CAS替换线程ID
* 5. 成功:偏向当前线程
* 6. 失败:存在竞争,升级为轻量级锁
*/
}
3.2.2 轻量级锁(Lightweight Locking)
java
public class LightweightLockingJDK16 {
/**
* 轻量级锁优化:
* 1. 针对线程交替执行同步块的场景
* 2. 使用CAS操作避免操作系统阻塞
* 3. 在用户态完成锁获取
*
* 核心机制:对象头Mark Word复制到线程栈帧的锁记录中
*/
public static void testLightweightLock() {
Object lock = new Object();
// 线程交替执行,使用轻量级锁
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 3; j++) {
synchronized (lock) {
System.out.println(Thread.currentThread().getName()
+ " 第" + j + "次获取锁");
try {
Thread.sleep(100); // 短时间持有
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
/**
* 轻量级锁加锁流程:
* 1. 在栈帧中创建锁记录(Lock Record)
* 2. 将对象头Mark Word复制到锁记录(Displaced Mark Word)
* 3. CAS尝试将对象头指向锁记录
* 4. 成功:获取轻量级锁
* 5. 失败:检查是否重入,否则升级为重量级锁
*
* 解锁流程:
* 1. CAS将Displaced Mark Word写回对象头
* 2. 成功:解锁完成
* 3. 失败:说明已升级为重量级锁,需要唤醒等待线程
*/
}
}
3.2.3 自旋锁与适应性自旋
java
public class SpinLockJDK16 {
/**
* 自旋锁优化:
* 1. 避免线程在用户态和内核态之间切换
* 2. 线程在等待时执行忙循环(自旋)
*
* 适应性自旋(Adaptive Spinning):
* 1. 根据前一次自旋等待时间动态调整
* 2. 如果自旋很少成功,则减少自旋次数
* 3. 如果自旋经常成功,则增加自旋次数
*
* 参数:
* -XX:+UseSpinning // 启用自旋(JDK 1.6默认)
* -XX:PreBlockSpin=10 // 默认自旋次数
*/
public static void demonstrateAdaptiveSpin() {
Object lock = new Object();
// 线程1长时间持有锁
new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程1长时间持有锁");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 给线程1启动时间
try { Thread.sleep(100); } catch (InterruptedException e) {}
// 线程2尝试获取锁
new Thread(() -> {
long start = System.currentTimeMillis();
synchronized (lock) {
long end = System.currentTimeMillis();
System.out.println("线程2等待时间:" + (end - start) + "ms");
// 第一次自旋可能失败,JVM会记录并减少后续自旋时间
}
}).start();
}
}
3.2.4 锁消除(Lock Elimination)
java
public class LockEliminationJDK16 {
/**
* 锁消除优化:
* 1. JIT编译器进行的优化
* 2. 基于逃逸分析(Escape Analysis)
* 3. 消除不可能存在共享资源竞争的锁
*
* 启用参数:
* -XX:+DoEscapeAnalysis // 逃逸分析(默认开启)
* -XX:+EliminateLocks // 锁消除(默认开启)
*/
// 示例1:StringBuffer局部变量
public String concatStrings(String s1, String s2, String s3) {
// StringBuffer是线程安全的,但这里的sb不会逃逸出方法
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
sb.append(s3);
return sb.toString(); // JIT会消除synchronized锁
}
// 示例2:无竞争的同步块
public void noContention() {
Object lock = new Object(); // 局部对象,不会逃逸
synchronized (lock) {
// 只有一个线程能访问此方法,锁会被消除
System.out.println("无竞争的锁会被消除");
}
}
// 对比:锁不会被消除的情况
private final Object sharedLock = new Object(); // 共享对象
public void withContention() {
synchronized (sharedLock) {
// 多个线程可能竞争,锁不会消除
System.out.println("共享锁不会被消除");
}
}
}
3.2.5 锁粗化(Lock Coarsening)
java
public class LockCoarseningJDK16 {
/**
* 锁粗化优化:
* 1. 将连续的多个锁操作合并为一个
* 2. 减少锁的获取和释放次数
* 3. 提升性能(尤其在循环中)
*/
// 优化前:多次锁获取/释放
public void beforeCoarsening() {
Object lock = new Object();
// 连续的同步块
synchronized (lock) {
System.out.println("操作1");
}
synchronized (lock) {
System.out.println("操作2");
}
synchronized (lock) {
System.out.println("操作3");
}
// JIT可能会优化为:
// synchronized (lock) {
// System.out.println("操作1");
// System.out.println("操作2");
// System.out.println("操作3");
// }
}
// 循环中的锁粗化
public void loopCoarsening() {
Object lock = new Object();
// 在循环内加锁(可能被粗化到循环外)
for (int i = 0; i < 1000; i++) {
synchronized (lock) {
// 简单操作
System.out.println(i);
}
}
// JIT可能优化为:
// synchronized (lock) {
// for (int i = 0; i < 1000; i++) {
// System.out.println(i);
// }
// }
}
// 注意:锁粗化的边界条件
public void coarseningBoundary() {
Object lock = new Object();
// 中间有耗时操作,可能不会被粗化
synchronized (lock) {
System.out.println("操作1");
}
expensiveOperation(); // 耗时操作
synchronized (lock) {
System.out.println("操作2");
}
// 这种情况JIT通常不会粗化
}
private void expensiveOperation() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.3 JDK 8:进一步优化
java
public class SynchronizedJDK8 {
/**
* JDK 8 优化:
* 1. 改进的锁消除和锁粗化
* 2. 更好的逃逸分析
* 3. 默认开启所有优化
*
* 重要参数变化:
* -XX:+DoEscapeAnalysis // 默认开启
* -XX:+EliminateLocks // 默认开启
* -XX:+EliminateAllocations // 标量替换,默认开启
*/
// 标量替换(Scalar Replacement)示例
public void scalarReplacement() {
Point point = new Point(10, 20);
int x = point.x; // 直接使用基本类型,对象被消除
int y = point.y;
System.out.println(x + y);
}
static class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
}
3.4 JDK 15:偏向锁被废弃
java
public class BiasedLockDeprecationJDK15 {
/**
* JDK 15 重要变化:
* 1. 默认禁用偏向锁:-XX:-UseBiasedLocking
* 2. 废弃偏向锁相关代码
* 3. 未来版本可能移除
*
* 原因:
* 1. 现代应用竞争激烈,偏向锁很少受益
* 2. 偏向锁撤销开销大
* 3. 保持偏向锁状态需要额外的维护
* 4. 与新的ZGC不兼容
*/
public static void showCurrentDefaults() {
// JDK 15+ 默认配置:
// -XX:+UseBiasedLocking false
// -XX:+UseHeavyMonitors false(使用轻量级锁)
System.out.println("JDK 15+ 偏向锁默认禁用");
System.out.println("锁升级路径:无锁 → 轻量级锁 → 重量级锁");
}
}
4. 现代 JVM 锁状态转换
4.1 完整的锁升级流程(JDK 15+)
java
public class ModernLockUpgrade {
/**
* 现代JVM锁状态转换(JDK 15+):
*
* 1. 无锁状态 (01)
* ↓ 线程请求锁
* 2. 轻量级锁 (00) ←→ 无锁
* ↓ 竞争加剧
* 3. 重量级锁 (10)
*
* 注意:偏向锁已被废弃
*/
// 演示锁升级过程
public static void demonstrateLockUpgrade() {
Object lock = new Object();
// 阶段1:无竞争,使用轻量级锁
new Thread(() -> {
synchronized (lock) {
System.out.println("线程1:轻量级锁");
}
}).start();
try { Thread.sleep(100); } catch (InterruptedException e) {}
// 阶段2:轻微竞争,可能保持轻量级锁
for (int i = 0; i < 3; i++) {
new Thread(() -> {
synchronized (lock) {
System.out.println("线程" + Thread.currentThread().getId()
+ ":可能轻量级锁");
}
}).start();
try { Thread.sleep(50); } catch (InterruptedException e) {}
}
// 阶段3:激烈竞争,升级为重量级锁
try { Thread.sleep(1000); } catch (InterruptedException e) {}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (lock) {
System.out.println("线程" + Thread.currentThread().getId()
+ ":可能重量级锁");
try {
Thread.sleep(100); // 模拟工作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
5. 性能对比与最佳实践
5.1 性能对比测试
java
public class SynchronizedPerformance {
private static final int ITERATIONS = 10_000_000;
private int counter = 0;
private final Object lock = new Object();
// 测试1:无同步(基准)
public void testNoSync() {
for (int i = 0; i < ITERATIONS; i++) {
counter++;
}
}
// 测试2:方法级同步
public synchronized void testMethodSync() {
for (int i = 0; i < ITERATIONS; i++) {
counter++;
}
}
// 测试3:代码块同步
public void testBlockSync() {
for (int i = 0; i < ITERATIONS; i++) {
synchronized (this) {
counter++;
}
}
}
// 测试4:粗粒度同步
public void testCoarseSync() {
synchronized (this) {
for (int i = 0; i < ITERATIONS; i++) {
counter++;
}
}
}
public static void main(String[] args) {
SynchronizedPerformance test = new SynchronizedPerformance();
// 测试不同场景下的性能
long start, end;
// 单线程测试
start = System.currentTimeMillis();
test.testNoSync();
end = System.currentTimeMillis();
System.out.println("无同步: " + (end - start) + "ms");
// 重置
test.counter = 0;
start = System.currentTimeMillis();
test.testMethodSync();
end = System.currentTimeMillis();
System.out.println("方法同步: " + (end - start) + "ms");
// 更多测试...
}
}
5.2 最佳实践
java
public class SynchronizedBestPractices {
// 1. 尽量减小同步范围
public void minimizeScope() {
// 不好:同步整个方法
// public synchronized void process() { /* 大量代码 */ }
// 好:只同步必要部分
public void process() {
// 非同步代码
// ...
synchronized (this) {
// 只同步共享资源访问
}
// 更多非同步代码
}
}
// 2. 使用不同的锁对象
public class SeparateLocks {
private final Object readLock = new Object();
private final Object writeLock = new Object();
private int readCount = 0;
private int writeCount = 0;
public void incrementRead() {
synchronized (readLock) {
readCount++;
}
}
public void incrementWrite() {
synchronized (writeLock) {
writeCount++;
}
}
}
// 3. 避免在循环内同步
public void avoidSyncInLoop() {
Object lock = new Object();
List<String> data = new ArrayList<>();
// 不好:每次迭代都获取/释放锁
// for (String item : data) {
// synchronized (lock) {
// process(item);
// }
// }
// 好:批量处理
synchronized (lock) {
for (String item : data) {
process(item);
}
}
}
// 4. 使用private final锁对象
public class PrivateFinalLock {
private final Object lock = new Object(); // 私有final,防止外部修改
public void safeMethod() {
synchronized (lock) {
// 线程安全
}
}
// 避免使用String/Integer等作为锁
// private final String badLock = "LOCK"; // 可能导致意外同步
}
// 5. 考虑使用java.util.concurrent
public void considerConcurrentPackage() {
// 高竞争场景:使用ReentrantLock
// ReentrantLock lock = new ReentrantLock();
// lock.lock();
// try {
// // 临界区
// } finally {
// lock.unlock();
// }
// 读写分离:使用ReadWriteLock
// ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
}
private void process(String item) {
// 处理逻辑
}
}
6. 监控与调试
6.1 查看锁状态
java
public class LockStateInspection {
// 使用jstack查看线程状态
public static void inspectLockState() {
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("线程1持有锁");
Thread.sleep(5000); // 长时间持有
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程2获取到锁");
}
});
t1.start();
try { Thread.sleep(100); } catch (InterruptedException e) {}
t2.start();
// 此时运行:jstack <pid> 可以看到:
// "Thread-0" 状态:TIMED_WAITING (sleeping),持有锁
// "Thread-1" 状态:BLOCKED (on object monitor),等待锁
}
// 使用ManagementFactory监控
public static void monitorWithJMX() throws Exception {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 查找死锁
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreads);
for (ThreadInfo info : threadInfos) {
System.out.println("死锁线程: " + info.getThreadName());
System.out.println("锁信息: " + info.getLockInfo());
System.out.println("锁拥有者: " + info.getLockOwnerName());
}
}
}
}
6.2 诊断工具
java
# 1. jstack - 查看线程栈和锁信息
jstack <pid>
# 2. jcmd - 多功能诊断
jcmd <pid> Thread.print # 等同于jstack
jcmd <pid> VM.flags # 查看VM参数
# 3. JConsole - 图形化监控
jconsole
# 4. VisualVM - 高级分析
jvisualvm
# 5. arthas - 阿里开源的Java诊断工具
# 在线诊断,无需重启应用
7. 总结
7.1 优化历程总结
| JDK版本 | 主要优化 | 影响 |
|---|---|---|
| 1.0-1.5 | 重量级锁 | 性能差,需要内核切换 |
| 1.6 | 偏向锁、轻量级锁、自旋锁、锁消除、锁粗化 | 性能大幅提升 |
| 8 | 改进逃逸分析,默认开启所有优化 | 进一步优化 |
| 15 | 默认禁用偏向锁 | 简化锁实现,更好适应现代应用 |
7.2 当前最佳配置(JDK 17+)
java
# 现代JVM推荐配置
-XX:+UseCompressedOops # 压缩指针(默认开启)
-XX:+DoEscapeAnalysis # 逃逸分析(默认开启)
-XX:+EliminateLocks # 锁消除(默认开启)
-XX:+EliminateAllocations # 标量替换(默认开启)
-XX:-UseBiasedLocking # 禁用偏向锁(JDK 15+默认)
7.3 性能建议
-
优先使用轻量级同步:volatile、原子类
-
合理使用synchronized:减小同步范围
-
高竞争场景:考虑ReentrantLock
-
读写分离:使用ReadWriteLock
-
监控和测试:定期检查锁竞争情况
Synchronized经过多年发展,已经从一个性能较差的重量级锁,优化为智能高效的同步机制。理解其工作原理和优化历程,有助于编写高性能的并发程序。