目录
- 引言
- 一、为什么需要synchronized?
- 二、synchronized的三种使用方式
- 三、synchronized底层原理
- 四、锁优化:JDK的不断进化
- 五、synchronized vs Lock
- 六、volatile的辅助作用
- 总结与展望
- 互动环节
引言
在多线程编程中,最令人头疼的问题莫过于数据竞争和线程安全。当我们多个线程同时读写同一个共享变量时,结果往往变得不可预测,这就是线程不安全的表现。
synchornized关键字作为Java语言内置的同步锁机制,从诞生之初就肩负着解决线程安全问题的重任。它就像交通信号灯,让并发的线程变得有序,避免"交通事故"的发生。本文将带你深入理解这个Java并发编程中最基础、最重要的关键字。
一、为什么需要synchronized?
先来看一个经典的线程不安全例子:
public class UnsafeCounter {
private int count = 0;
public void increment() {
count++; // 这行代码不是原子操作!
}
public int getCount() {
return count;
}
}
count++这行代码看似简单,实际上包含了三个操作:
- 读取count的当前值
- 将值加1
- 将新值写回count
在多线程环境下,两个线程可能同时读取到相同的值,然后各自加1后写回,导致最终结果比预期少。
synchronized的作用就是保证同一时刻,只有一个线程可以执行某个方法或代码块,从而避免这种数据竞争问题。
二、synchronized的三种使用方式
1. 同步实例方法
public class SafeCounter {
private int count = 0;
// 同步实例方法,锁是当前对象实例(this)
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
2. 同步静态方法
public class StaticCounter {
private static int count = 0;
// 同步静态方法,锁是当前类的Class对象
public static synchronized void increment() {
count++;
}
}
3. 同步代码块
public class FlexibleCounter {
private int count = 0;
private final Object lock = new Object(); // 专门的锁对象
public void increment() {
// 一些非同步操作...
synchronized (lock) { // 同步代码块,锁的是lock对象
count++; // 只有这部分需要同步
}
// 其他非同步操作...
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
同步代码块的优势:
- 更细的锁粒度,减少锁竞争
- 可以选择不同的锁对象,提高灵活性
- 性能通常比同步方法更好
三、synchronized底层原理
1. 对象头与Monitor
在JVM中,每个对象都有一个对象头,其中包含Mark Word,用于存储对象的哈希码、分代年龄和锁标志位。
当线程进入synchronized代码块时:
- 尝试获取对象的Monitor(监视器锁)
- 如果获取成功,将Mark Word指向Monitor的指针
- 如果获取失败,线程进入阻塞状态
2. 字节码层面
编译器会在synchronized代码块的前后插入monitorenter和monitorexit指令:
public void test();
Code:
0: aload_0
1: getfield #2 // 获取lock字段
4: dup
5: astore_1
6: monitorenter // 进入同步块
7: aload_0
8: dup
9: getfield #3 // 获取count字段
12: iconst_1
13: iadd
14: putfield #3 // 设置count字段
17: aload_1
18: monitorexit // 正常退出同步块
19: goto 27
22: astore_2
23: aload_1
24: monitorexit // 异常退出同步块(保证锁释放)
25: aload_2
26: athrow
27: return
四、锁优化:JDK的不断进化
早期的synchronized是"重量级锁",性能较差。经过多个JDK版本的优化,现在它的性能已经非常优秀。
1. 锁升级过程
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
// 这个示例演示了不同锁状态的变化
public class LockUpgradeDemo {
private static final Object lock = new Object();
private static int count = 0;
public static void main(String[] args) {
// 第一阶段:无竞争,偏向锁
synchronized (lock) {
count++;
}
// 第二阶段:轻微竞争,轻量级锁
for (int i = 0; i < 2; i++) {
new Thread(() -> {
synchronized (lock) {
count++;
}
}).start();
}
// 第三阶段:激烈竞争,重量级锁
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (lock) {
count++;
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
2. 其他优化技术
- 锁消除:JVM检测到不可能存在共享数据竞争时,会消除锁
- 锁粗化:将连续的加锁解锁操作合并为一次加锁操作
- 自适应自旋:根据以往锁竞争情况动态调整自旋次数
五、synchronized vs Lock
特性 | synchronized | Lock(如ReentrantLock) |
实现层面 | JVM内置,关键字 | JDK API,接口 |
锁获取 | 自动获取和释放 | 需要手动lock()和unlock() |
灵活性 | 相对简单,功能有限 | 功能丰富,支持尝试锁、超时锁等 |
性能 | JDK6后优化很好 | 在高竞争环境下可能更好 |
中断响应 | 不支持 | 支持锁获取中断 |
公平性 | 非公平锁 | 可选择公平或非公平 |
选择建议:
- 大多数情况下,优先选择synchronized,更简单可靠
- 需要高级功能(如超时、中断等)时,选择Lock
- 在高度竞争的场景下,可以测试两者的性能差异
六、volatile的辅助作用
虽然本文重点是synchronized,但有必要提一下它的"好搭档"——volatile。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
在这个经典的双重检查锁模式中:
- synchronized保证创建实例的线程安全
- volatile防止指令重排序,保证其他线程看到的是完全初始化后的实例
volatile保证可见性和有序性,但不保证原子性,它适合用作状态标志位:
public class StoppableTask {
private volatile boolean stopped = false;
public void run() {
while (!stopped) {
// 执行任务
}
}
public void stop() {
stopped = true; // 其他线程立即可见
}
}
总结与展望
synchronized作为Java并发编程的基石,经历了从"性能杀手"到"高效同步工具"的华丽转身。它的主要优势在于:
- 简单易用:语法简单,自动释放锁
- JVM支持:内置优化,如锁升级、锁消除等
- 可靠性高:不容易出现锁泄漏等问题
最佳实践:
- 优先使用同步代码块,减小锁粒度
- 使用私有对象作为锁,避免外部干扰
- 同步块内尽量少做耗时操作
未来展望:
随着Project Loom的推进,未来可能会有更轻量的并发模型,但synchronized作为基础同步机制,仍将长期发挥重要作用。
理解synchronized不仅是为了应对面试,更是为了写出正确、高效的多线程程序。它是每个Java开发者必须掌握的基本功。
互动环节
你在使用synchronized的过程中遇到过什么有趣的问题或者坑?有没有因为锁使用不当导致过性能问题?欢迎在评论区分享你的实战经验,我们一起交流学习!