第2章 Java与Native线程同步基础:synchronized、ReentrantLock、Condition、Mutex、读写锁
SystemUI作为系统持久层界面,运行空间横跨Java(SystemServer/SystemUI)、Native(SurfaceFlinger/渲染线程)以及JNI中继层。任何一个同步原语使用不当,都可能导致UI线程阻塞、通知面板卡顿甚至系统重启。本章从Java和Native两个维度对比核心同步原语,建立跨语言“翻译”能力,为后续分析Binder调度与线程死锁夯实基础。
本章目标:掌握Java与C++同步原语的映射关系,能够在SystemUI源码中快速识别锁的使用方式,并具备跨语言死锁排查的初步能力。
2.1 Java层同步原语
SystemUI中大量使用Java同步机制保护通知集合、面板状态等共享对象。理解其底层的“锁升级”过程对性能调优至关重要。
- synchronized:基于对象Monitor实现。JVM偏向锁→轻量锁(CAS自旋)→重量锁(挂起线程)。适合临界区小、竞争低的场景。
- ReentrantLock:基于AQS(AbstractQueuedSynchronizer)实现。支持公平/非公平、可中断、多Condition绑定。SystemUI中较复杂的状态保护常用此锁。
- Condition:必须与ReentrantLock配合使用。经典生产者-消费者模式,提供
await()/signal()/signalAll(),比Object.wait()更灵活且支持超时。 - ReentrantReadWriteLock:读读不互斥,读写/写写互斥。适用于读多写少的场景(如常量配置、缓存数据)。
2.2 Native层同步原语
SystemUI的渲染管线(RenderThread)与部分系统服务(SurfaceFlinger、InputFlinger)运行在Native层。JNI临界区操作也直接依赖这些原语。
- std::mutex / pthread_mutex_t:C++11标准封装,底层调用futex快速路径。建议配合
std::lock_guard或std::unique_lock实现RAII管理。 - std::condition_variable:必须与
std::unique_lock<std::mutex>配合。wait()自动解锁并在唤醒时重新加锁,有效避免信号丢失。 - pthread_rwlock_t:读写锁Native实现。需注意其偏向写者,在某些实现中可能造成读者饥饿。
性能直觉: Java
synchronized 在低竞争时通过偏向锁/轻量锁实现几乎无开销;Native std::mutex 在无竞争时仅需原子操作 + 用户空间CAS,通常在10-100ns级别。高竞争下两者均会陷入内核(futex),SystemUI应尽量避免高竞争锁设计。
2.3 核心对比表:synchronized vs ReentrantLock vs std::mutex
| 维度 | Java synchronized | Java ReentrantLock | Native std::mutex |
|---|---|---|---|
| 可中断 | 不支持(除非使用Lock API) | 支持 (lockInterruptibly) | 不支持(除非调时版本) |
| 公平性 | 非公平 | 可配置为公平 | 非公平 |
| 条件变量 | Object.wait/notify | 多Condition绑定 | condition_variable |
| 低竞争开销 | 偏向锁 ≈ 10ns | CAS ≈ 20ns | 用户空间CAS ≈ 10ns |
| 高竞争开销 | 重量锁 (内核挂起) | AQS队列 + park | futex (内核挂起) |
| 使用约束 | JVM自动加解锁 | 必须在finally中解锁 | RAII或手动解锁 |
| 调试友好 | Thread Dump直接显示 | 依赖AQS工具类 | ptrace / debuggerd |
2.4 跨语言映射与JNI临界区实战
在SystemUI开发中,JNI临界区是跨语言死锁的重灾区。Java通过JNI调用Native函数时,如果Native函数需要获取Java对象的Monitor,就必须使用MonitorEnter/MonitorExit。反之,Native代码回调Java方法时,也容易在Java层造成意外阻塞。
JNI死锁经典陷阱: Native线程A持有Native锁L1,尝试通过JNI获取Java对象M1的Monitor。Java线程B持有M1的Monitor,尝试调用Native方法获取锁L1。此为AB-BA死锁。务必保证跨语言加锁顺序全局一致,或使用
JNIEnv::ExceptionCheck检测异常。
Java端典型使用:SystemUI中的任务队列等待
public class TaskCompletionController {
private final ReentrantLock lock = new ReentrantLock();
private final Condition allTasksDone = lock.newCondition();
private int pendingTaskCount = 0;
public void waitForCompletion(long timeoutMs) throws InterruptedException {
lock.lock();
try {
while (pendingTaskCount > 0) {
if (!allTasksDone.await(timeoutMs, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("任务执行超时");
}
}
} finally {
lock.unlock();
}
}
public void onTaskFinished() {
lock.lock();
try {
if (--pendingTaskCount == 0) {
allTasksDone.signalAll();
}
} finally {
lock.unlock();
}
}
}
Native端等价的实现方式:
class TaskCompletionController {
public:
void waitForCompletion(std::chrono::milliseconds timeout) {
std::unique_lock<std::mutex> lock(mtx_);
while (pending_task_count_ > 0) {
if (cv_.wait_for(lock, timeout) == std::cv_status::timeout) {
throw std::runtime_error("任务执行超时");
}
}
}
void onTaskFinished() {
std::lock_guard<std::mutex> lock(mtx_);
if (--pending_task_count_ == 0) {
cv_.notify_all();
}
}
private:
std::mutex mtx_;
std::condition_variable cv_;
int pending_task_count_ = 0;
};
2.5 调试与排查要点
- Java线程转储:
kill -3 PID或adb shell dumpsys activity service SystemUIService,直接显示锁等待链。 - Native奔溃分析:
debuggerd -b PID可打印Native线程堆栈与锁持有信息(需要开启libc.debug.malloc)。 - Perfetto / Systrace:通过
adb shell perfetto追踪futex系统调用,识别高竞争锁热点。 - 常见日志关键字:
Waiting on condition、held by thread、monitor contention、blocked on lock。
稳定性建议: SystemUI中所有可能阻塞主线程的锁操作,建议加超时保护(例如
tryLock或await(timeout)),防止线程永久挂起。对于高频访问的共享变量(如面板可见性标志),优先考虑volatile或AtomicBoolean,避免锁膨胀。