Java Thread线程2—线程锁synchronized,Lock,volatile
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 checksynchronized (Singleton.class) {if (INSTANCE == null) { // 2nd checkINSTANCE = 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) { // 看到非 nullinstance.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。”