1. 为什么需要 synchronized?
在多线程环境下,多个线程可能同时访问和修改共享资源,导致数据不一致的问题。
例如:两个线程同时对同一个银行账户余额进行“取款”操作,可能因为操作的非原子性(读-改-写)导致最终结果错误。
synchronized的作用就是保证同一时刻最多只有一个线程执行被它修饰的代码块或方法,从而保证线程安全。
2. synchronized 的三种使用方式
| 使用方式 | 锁对象 | 作用范围 |
|---|---|---|
| 修饰实例方法 | 当前实例对象this | 整个方法体 |
| 修饰静态方法 | 当前类的Class对象 | 整个方法体 |
| 修饰代码块 | 指定的任意对象 | 代码块内的语句 |
2.1 修饰实例方法 -- 锁对象是整个 this
public class Counter { private int count = 0; public synchronized void increment() { // 锁住 this count++; } }2.2 修饰静态方法 -- 锁对象是整个类
public class Counter { private static int count = 0; public static synchronized void increment() { // 锁住 Counter.class count++; } }2.3 修饰代码块(最灵活)-- 锁对象是lock,细粒度锁,只锁这一个特定的锁对象
//只有显式写上 synchronized(lock)的代码块才会排队。其他方法(包括synchronized方法)完全不受影响,可以并行执行。public class Counter { private int count = 0; private final Object lock = new Object(); public void increment() { synchronized (lock) { // 锁住 lock 对象 count++; } } }3. 锁的是什么?(核心要点)
synchronized 不是锁代码,而是锁对象。
同一时刻,同一个锁对象只能被一个线程持有。
不同锁对象之间互不干扰。
示例说明:
public class Example { public synchronized void methodA() {} // 锁 this public synchronized void methodB() {} // 锁 this(同一个锁) public static synchronized void methodC() {} // 锁 Class }线程1 执行
methodA()时,线程2 不能执行methodB()(因为锁是同一个this,如果是不同的“this”,则两个方法并不会相互影响)。但线程2 可以执行
methodC()(因为锁是Class,不同锁对象)。
4. 底层实现原理(JVM层面)
监视器锁(Monitor):每个Java对象都有一个内部锁(Monitor)。
synchronized本质就是获取/释放这个Monitor。字节码指令:
同步代码块:
monitorenter进入,monitorexit退出(可能有多个退出路径)。同步方法:通过方法表的
ACC_SYNCHRONIZED标志隐式实现。
锁升级机制(JDK 1.6+ 优化):无锁 → 偏向锁 → 轻量级锁 → 重量级锁(自旋、自适应自旋、锁粗化、锁消除等优化)。
5. 与 volatile 的区别
| 特性 | synchronized | volatile |
|---|---|---|
| 原子性 | ✅ 保证 (要么全做,要么全不做) | ❌ 不保证 |
| 可见性 | ✅ 保证(解锁前刷新到主存,改了就得让人看见) | ✅ 保证 |
| 有序性 | ✅ 保证(同一时刻单线程执行,代码按顺序执行,不被重排) | ✅ 保证(禁止指令重排序) |
| 是否阻塞 | 是(竞争失败会阻塞,获取不到锁时,线程是等待还是放弃) | 否 |
简单记:
volatile解决可见性,synchronized解决可见性 + 原子性。
6. 常见误区与注意事项(可以通过AI简单了解)
锁对象不能是 null,否则抛
NullPointerException。锁对象尽量使用
final,避免引用被修改导致锁失效。不要用
String常量或Integer等包装类作为锁,因为常量池可能导致意外共享锁。synchronized 是可重入锁,同一个线程可以多次获取同一把锁(避免死锁)。
过度使用会降低性能,应尽量缩小同步范围。
7. 简单示例:线程安全的计数器
public class SafeCounter { private int count = 0; private final Object lock = new Object(); public void increment() { synchronized (lock) { count++; } } public int getCount() { synchronized (lock) { return count; } } }8. 最后一句话:在真正的互联网高并发业务代码中,我们其实很少直接用synchronized处理业务逻辑
synchronized 是 Java 内置的互斥锁,通过锁住对象来保证代码块或方法在并发环境下的原子性、可见性和有序性,是解决线程安全问题最直接、最可靠的手段之一。
分布式场景:现在的服务大多是分布式部署(多台机器),
synchronized只能锁单台机器的JVM进程,跨机器无效。我们通常用Redis分布式锁或ZooKeeper。单机高性能场景:如果是单机高并发计数器、累加器,我们通常直接用
AtomicInteger(CAS无锁)或LongAdder,性能比synchronized高得多。需要等待/超时/中断的场景:我们会用
ReentrantLock(因为它支持尝试加锁、超时等待、可中断),而synchronized不支持这些高级功能。