Blankj / Blankj/AndroidUtilCode
[Bug] TimeUtils 中 ThreadLocal 缓存导致 Locale/TimeZone 状态永久滞留
Open
@Blankj is already working on this.
Since Feb 22, 2026.
bug
- Dominant language
- Java
- Stars
- 33.6k
- Forks
- 10.6k
- PR merge metrics
- No merged PRs in 30d
Description
描述 Bug
在 TimeUtils.java 中,为了优化性能使用了 ThreadLocal<Map<String, SimpleDateFormat>> 来缓存时间格式化器。但在执行 new SimpleDateFormat(pattern) 时,实例会隐式依赖并绑定当前线程创建时的系统全局 Locale 和 TimeZone。
在 Android 的多线程环境中,后台工作线程(如线程池)是长期存活且高度复用的。如果用户在应用运行期间切换了系统语言或跨越了物理时区,系统分配的复用旧线程仍会盲目使用 ThreadLocal 中残留的旧格式化器进行渲染。这会导致同一个应用界面上由于多线程执行出现“表现层分裂”(如部分输出法文日期,部分输出英文日期),严重破坏数据一致性和用户体验,且常规代码审查极难察觉。
- AndroidUtilCode 的版本:- 出现 Bug 的设备型号:- 设备的 Android 版本:## 相关代码
问题代码定位 (TimeUtils.java):
public static SimpleDateFormat getSafeDateFormat(String pattern) {
Map<String, SimpleDateFormat> sdfMap = SDF_THREAD_LOCAL.get();
SimpleDateFormat simpleDateFormat = sdfMap.get(pattern);
if (simpleDateFormat == null) {
// 缺陷引爆点:隐式绑定了早期的 Locale 和 TimeZone,且被永久缓存
simpleDateFormat = new SimpleDateFormat(pattern);
sdfMap.put(pattern, simpleDateFormat);
}
return simpleDateFormat;
}
修复建议:
不能仅仅依赖 pattern 作为缓存的 Key。建议将缓存的 Key 改为 pattern + Locale.getDefault() + TimeZone.getDefault() 的复合键,或者在每次 get() 时校验当前系统的语言/时区是否发生变更,如果变更则使当前线程的缓存失效。
异常堆栈:
无崩溃堆栈。
这是一个导致微观内存维度发生静默状态漂移的逻辑缺陷,表现为多线程并发下的 UI 渲染错乱和数据格式异常。
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.