You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

多回调引发ConcurrentModificationException,如何避免SharedPreference并发冲突?

解决多回调并发修改SharedPreference引发的ConcurrentModificationException问题

嘿,我来帮你搞定这个并发修改的问题!你现在遇到的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

火山引擎 最新活动