多回调引发ConcurrentModificationException,如何避免SharedPreference并发冲突?
嘿,我来帮你搞定这个并发修改的问题!你现在遇到的ConcurrentModificationException,本质上是因为多个回调可能在不同线程同时操作同一个SharedPreference——虽然SharedPreference的Editor看起来是单线程安全的,但如果多个异步回调同时读写,就会触发底层数据结构的并发冲突。下面给你几个靠谱的解决方案:
1. 用同步锁(Synchronized)控制并发访问
最简单直接的方式就是给SharedPreference的修改操作加个全局锁,确保同一时间只有一个线程能执行修改逻辑。
首先定义一个全局的锁对象(比如在你的Activity/Fragment或者单例类里):
private final Object spLock = new Object();
然后在每个回调的onResult方法里,用这个锁包裹修改逻辑:
@Override public void onResult(String info, float v) { synchronized(spLock) { // 获取SharedPreference实例 SharedPreferences sp = getSharedPreferences("your_sp_name", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); // 执行你的修改操作,比如: editor.putString("target_key", "new_value"); editor.putFloat("another_key", v); // 提交修改,推荐用apply()(异步不阻塞),加锁后也可以用commit() editor.apply(); } }
这个方式的核心是通过锁把并发操作变成串行,彻底避免多个线程同时修改SharedPreference的情况。
2. 用单线程执行器(Executor)统一处理修改任务
如果不想手动加锁,你可以把所有修改SharedPreference的任务都放到同一个单线程里执行,这样任务会自动排队,自然不会有并发冲突。
先初始化一个单线程的ExecutorService:
private final ExecutorService spUpdateExecutor = Executors.newSingleThreadExecutor();
然后在回调里把修改逻辑提交到这个执行器:
@Override public void onResult(String info, float v) { spUpdateExecutor.execute(() -> { SharedPreferences sp = getSharedPreferences("your_sp_name", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); // 执行修改操作 editor.putString("target_key", info); editor.putFloat("value_key", v); editor.apply(); }); }
这种方式更优雅,不用手动管理锁,所有修改任务都会按顺序在同一个线程执行,安全又省心。
3. 用Jetpack DataStore替代SharedPreference(推荐长期方案)
如果你项目已经在用Jetpack组件,那强烈建议换成DataStore——它就是为了解决SharedPreference的并发问题设计的,基于协程和Flow,天生支持原子性的并发读写操作,完全不会出现ConcurrentModificationException。
举个Kotlin的Preferences DataStore示例(Java可以用RxJava版本):
首先定义DataStore实例:
import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.preferencesDataStore // 在Context的扩展里定义DataStore val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "your_data_store_name") // 定义键 val TARGET_KEY = stringPreferencesKey("target_key") val VALUE_KEY = stringPreferencesKey("value_key")
然后在回调里(用协程执行修改):
viewModelScope.launch { context.dataStore.edit { preferences -> preferences[TARGET_KEY] = info preferences[VALUE_KEY] = v.toString() } }
DataStore的edit方法是原子操作,多个并发调用会自动排队处理,不仅解决了并发问题,还比SharedPreference更可靠(不会出现数据丢失的情况)。
额外注意点
- 如果你的回调都是在主线程执行的,其实本来不会有并发问题,但如果回调是异步的(比如网络请求、后台任务的回调),就必须用上面的方案处理。
- 尽量用
apply()代替commit()提交修改,apply()是异步提交到主线程队列,不会阻塞当前线程,性能更好;加锁后两种方式都安全,但apply()更推荐。
内容的提问来源于stack exchange,提问作者Toan Le




