synchronized
在 Java 中,synchronized 关键字一共有 4 种写法(按“锁的对象”划分),所有写法在 字节码层面本质相同:通过 对象头里的 monitor 来实现互斥
写法 1:同步实例方法(锁的是当前实例 this)
public class Counter {
private int count = 0;
public synchronized void increment() { // 等价于 synchronized(this) { ... }
count++;
}
}
-
锁对象:当前实例
this -
适用场景:多个线程操作同一个对象实例时保证线程安全。
同步静态方法(锁的是 Class 对象)
public class Counter {
private static int count = 0;
public static synchronized void increment() { // 等价于 synchronized(Counter.class) { ... }
count++;
}
}
-
锁对象:
Counter.class(类的元数据对象) -
适用场景:保护静态变量,防止多个线程同时修改类级别的共享数据。
同步代码块(锁的是任意指定对象)
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) { // 锁的是自定义的 lock 对象
count++;
}
}
}
-
锁对象:任意显式指定的对象(
lock、this、Counter.class等) -
优点:粒度更细,减少锁竞争范围,提高并发度。
同步代码块(锁的是 this)
public class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
}
效果与“同步实例方法”完全相同,只是写法不同。
总结对照表(面试速记)
| 写法 | 锁对象 | 适用场景 | 粒度 |
| 同步实例方法 | this | 保护实例变量 | 粗 |
| 同步静态方法 | Class<?> | 保护静态变量 | 粗 |
| 同步代码块(this) | this | 与同步实例方法等价 | 中 |
| 同步代码块(任意对象) | 任意对象 | 细粒度锁、减少竞争 | 细 |
锁与线程关系
只要多个方法共用同一把锁(比如默认的 this 或同一个 Class<?>),无论线程去访问哪一个带这把锁的方法,都必须串行地获取这把锁。
举个例子
public class Demo {
public synchronized void a() { /* ... */ }
public synchronized void b() { /* ... */ }
public synchronized void c() { /* ... */ }
}
-
三个方法都隐式锁
this。 -
线程 T1 执行
a(),线程 T2 想执行b(),线程 T3 想执行c()。 -
结果:T2、T3 都会被挡在门外,直到 T1 释放锁。
锁只认“对象”,不认“方法”;谁拿着同一把锁,谁就是临界区的唯一通行证。
Lock之ReentrantLock
基本范式(try-finally 必解锁)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 获取锁(阻塞直到成功)
try {
count++;
} finally {
lock.unlock(); // 必须放在 finally,防止异常死锁
}
}
}
最常用、最安全的写法;无论正常还是异常路径,都能保证解锁。
可中断锁(lockInterruptibly()
public void incrementWithInterrupt() throws InterruptedException {
lock.lockInterruptibly(); // 可被 Thread.interrupt() 打断
try {
count++;
} finally {
lock.unlock();
}
}
尝试非阻塞锁(tryLock())
public boolean tryIncrement() {
if (lock.tryLock()) { // 立即返回,不阻塞
try {
count++;
return true;
} finally {
lock.unlock();
}
} else {
System.out.println("没抢到锁,直接干别的");
return false;
}
}
常用于 避免死锁 或 带超时的快速失败 逻辑。
超时尝试锁(tryLock(long, TimeUnit))
public boolean incrementWithTimeout(long time, TimeUnit unit) throws InterruptedException {
if (lock.tryLock(time, unit)) { // 最多等待指定时间
try {
count++;
return true;
} finally {
lock.unlock();
}
} else {
System.out.println("等待超时,没抢到锁");
return false;
}
}
对比 synchronized 与 Lock
| 特性 | synchronized | Lock (ReentrantLock) |
| 语法 | 关键字 | API 调用 |
| 可中断 | ❌ | ✅ (lockInterruptibly) |
| 非阻塞尝试 | ❌ | ✅ (tryLock) |
| 超时等待 | ❌ | ✅ (tryLock(timeout)) |
| 公平策略 | ❌ | ✅ (new ReentrantLock(true)) |
| 可绑定多个条件队列 | ❌ | ✅ (newCondition) |
volatile
volatile 是 Java 提供的最轻量级同步机制,只保证“可见性”和“有序性”,不保证“原子性”。在 Android 开发里,正确使用可以省去不少 synchronized 或 Lock 的开销;用错则会埋坑
volatile 解决的是“一个线程改了,另一个线程立刻能看到”的问题,不能解决“多线程同时改”的问题。
| 作用点 | 是否保证 | 说明 |
| 可见性 | ✅ | 写后立即刷新到主存,读时从主存取 |
| 有序性(禁止重排) | ✅ | 插入内存屏障,防止指令重排 |
| 原子性 | ❌ | 复合操作(i++、check-then-act)仍不安全 |
Android 典型使用场景
状态标志位
线程 A 改标志,线程 B 立即读,只有一个线程写:
private volatile boolean isRunning = true;
public void stop() {
isRunning = false; // 线程 A
}
public void loop() {
while (isRunning) { // 线程 B
...
}
}
双重检查单例
class Singleton {
private static volatile Singleton INSTANCE;
public static Singleton get() {
if (INSTANCE == null) { // 1st check
synchronized (Singleton.class) {
if (INSTANCE == null) { // 2nd check
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
UI 可见性通信
Activity / Fragment 中后台线程更新单个变量状态:
private volatile int mProgress; // 下载进度
与 synchronized 的对比
| 维度 | volatile | synchronized |
| 作用范围 | 单个变量 | 代码块/方法 |
| 原子性 | ❌ | ✅ |
| 阻塞 | 不会 | 会 |
| 性能 | 极高 | 较低(有优化 |
volatile 能保证线程之间立即可见并禁止指令重排,适合一写多读的纯标志位或双重检查单例;一旦涉及复合操作,仍需 synchronized、Lock 或 Atomic* 来保证原子性。”
禁止重排
没有 volatile,DCL 可能因为“先赋值、后初始化”的重排而暴露半初始化对象;volatile 的内存屏障把这两步顺序钉死,彻底杜绝该隐患。
重排前后对比
| 正常顺序(程序员视角) | 重排后(编译器/CPU 视角) |
| 1. memory = allocate() | 1. memory = allocate() |
| 2. ctor(memory) | 3. instance = memory ← 引用先发布 |
| 3. instance = memory | 2. ctor(memory) ← 初始化延后 |
带来的并发 bug
// 线程 A 执行
instance = new Singleton(); // 内部被重排成 1→3→2
// 线程 B 几乎同时
if (instance != null) { // 看到非 null
instance.method(); // 实际对象还没初始化 → 崩溃或数据错误
}
加上 volatile 后
-
读写都插入 内存屏障,禁止上述重排。
-
线程 B 一定 看到“构造完成”的对象。
android中锁的使用
android 中使用lock锁多还是synchronized?
在 Android 日常业务代码 中,synchronized 远比 Lock 用得多;只有在需要 可中断、超时、公平锁、读写分离 等高级特性时,才会显式使用 ReentrantLock / ReentrantReadWriteLock。
统计数据与社区习惯
-
Google 官方示例、AOSP、Jetpack 源码里,95% 以上的同步场景都是
synchronized或volatile。 -
Kotlin 标准库提供的
@Synchronized、synchronized(lock){}进一步降低了使用门槛 -
。
-
高并发框架(如 OkHttp、Glide)内部可能用到
ReentrantLock,但普通业务层很少需要。
| 特点 | 对锁选择的影响 |
| UI 主线程不容阻塞 | 一般把耗时任务丢到线程池/HandlerThread,锁竞争通常较轻;synchronized 足够。 |
| 电量 & 内存敏感 | synchronized 在 ART 下经过 偏向锁、轻量级锁 优化,低开销;ReentrantLock 需要额外对象和队列,内存占用略高。 |
| 代码简洁性 | Android 开发节奏快,“写起来简单、维护成本低” 比极致性能更重要。 |
“在 Android 项目中,synchronized 是首选,简单可靠且 ART 已做大量优化;只有遇到 高并发读写、可中断、公平锁 等需求,才考虑 ReentrantLock 或 ReentrantReadWriteLock。”
1308

被折叠的 条评论
为什么被折叠?



