Moosphan / Android-Daily-Interview

:pushpin:每工作日更新一道 Android 面试题,小聚成河,大聚成江,共勉之~

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2019-03-28:SharedPreferences 是线程安全的吗?它的 commit 和 apply 方法有什么区别?

Moosphan opened this issue · comments

2019-03-28:SharedPreferences 是线程安全的吗?它的 commit 和 apply 方法有什么区别?
commented

context.getSharedPreferences()开始追踪的话,可以去到ContextImpl的getSharedPreferences(),最终发现SharedPreferencesImpl这个SharedPreferences的实现类,在代码中可以看到读写操作时都有大量的synchronized,因此它是线程安全的

commented

commit是同步写入,会返回执行结果,apply方法是异步写入,并不会返回执行结果;但是SharedPreferences文件的写入是全量写入,即使只是修改了其中一条key-value,也会执行全部的写入操作,因为SharedPreferences只能用于存储体积较小的数据,太大了就容易引发OOM,同时如果需要修改多条数据,必须使用Editor来一次性完成修改再提交

commented

SharedPreferences 是线程安全的 进程不安全的, commit 是同步写入,apply是异步写入。

commented

SharedPreferences 是线程安全的 进程不安全的, commit 是同步写入有返回值,apply是异步写入。

  1. apply没有返回值而commit返回boolean表明修改是否提交成功
  2. apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。

由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。

SP 是线程安全,非进程安全。commit 和 apply 的方法里面都一个 commitToMemory 方法,即把更新同步到内容。至于落地磁盘,commit 也并非完全同步,如果 commit 前有 apply 还未落盘,commit 会异步等待 apply 落盘之后在执行,内部通过一个计数器来判断。
详细见:https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/SharedPreferences.md

1.SharePreferences是线程安全的 里面的方法有大量的synchronized来保障。
2.SharePreferences不是进程安全的 即使你用了MODE_MULTI_PROCESS 。
3.第一次getSharePreference会读取磁盘文件,异步读取,写入到内存中,后续的getSharePreference都是从内存中拿了。
4.第一次读取完毕之前 所有的get/set请求都会被卡住 等待读取完毕后再执行,所以第一次读取会有ANR风险。
5.所有的get都是从内存中读取。
6.提交都是 写入到内存和磁盘中 。apply跟commit的区别在于
apply 是内存同步 然后磁盘异步写入任务放到一个单线程队列中 等待调用。方法无返回 即void
commit 内存同步 只不过要等待磁盘写入结束才返回 直接返回写入成功状态 true or false
7.从 Android N 开始, 不再支持 MODE_WORLD_READABLE & MODE_WORLD_WRITEABLE. 一旦指定, 会抛异常 。也不要用MODE_MULTI_PROCESS 迟早被放弃。
8.每次commit/apply都会把全部数据一次性写入到磁盘,即没有增量写入的概念 。 所以单个文件千万不要太大 否则会严重影响性能。
建议用微信的第三方MMKV来替代SharePreference

1.SharePreferences是线程安全的 里面的方法有大量的synchronized来保障。
2.commit是同步写入,会返回执行结果,apply方法是异步写入,并不会返回执行结果;
3.SharePreferences不是进程安全的 
4.SharedPreferences是以XML的格式以文件的方式自动保存的,在DDMS中的File Explorer中展开到/data/data/<package name>/shared_prefs下,可以看到一个叫做SETTING_Infos.xml的文件
总结比较不错文章可以看下面具体分析:
http://gityuan.com/2017/06/18/SharedPreferences/
我的项目应用场景:保存用户名字,密码,手势密码,json字符串,
如果非要保存一个你都不确定大小的东西我建议是,你单独开启一个新的sharedprefernces来保存,如果想把一个集合保存起来怎么保存,如果想把集合中在套一个map集合怎么保存,
private void setCateg(List<Map<String, String>> datalist) {
Gson gson = new Gson();
String s1 = gson.toJson(datalist);
LSharePreference.getInstance(getActivity()).setString(SharePreferenceName.PROGRESS, s1);

}
/**

  • 取出json
    */
    private List<Map<String, String>> getCateg(String dateJson) {
    List<Map<String, String>> datalist = new ArrayList<>();
    Gson gson = new Gson();
    datalist = gson.fromJson(dateJson, new TypeToken<List<Map<String, String>>>() {
    }.getType());
    return datalist;
    }

这样好处,跟坏,你是怎么认为,你觉的会怎么样?

楼上都说的很清楚了,这里补充一个问题,

SharedPreferences优化建议:
来源:http://gityuan.com/2017/06/18/SharedPreferences/#%E4%BA%94-%E6%80%BB%E7%BB%93

  1. 强烈建议不要在sp里面存储特别大的key/value, 有助于减少卡顿/anr
  2. 请不要高频地使用apply, 尽可能地批量提交;commit直接在主线程操作, 更要注意了
  3. 不要使用MODE_MULTI_PROCESS;
  4. 高频写操作的key与高频读操作的key可以适当地拆分文件, 由于减少同步锁竞争;
  5. 不要一上来就执行getSharedPreferences().edit(), 应该分成两大步骤来做, 中间可以执行其他代码.
  6. 不要连续多次edit(), 应该获取一次获取edit(),然后多次执行putxxx(), 减少内存波动; 经常看到大家喜欢封装方法, 结果就导致这种情况的出现.
  7. 每次commit时会把全部的数据更新的文件, 所以整个文件是不应该过大的, 影响整体性能;
commented

全量写入:无论是commit还是apply都是全量写入,所以最好一次性操作完数据再提交
安全:线程安全,进程不安全
同步异步:commit同步提交,apply异步提交
卡顿:如果文件太大,加载时间会边长,影响启动速度,处理数据不能太大,onPause的是会检查是否存储完毕

SharedPreferences是线程安全的 进程不安全 用于存储体积小的数据 apply异步提交先提交到内存 而后在异步提交到硬盘 commit是同步提交到硬盘并返回结果

SharedPreferences为什么是进程不安全?
1) SharedPreferences是进程不安全的,因为没有使用跨进程的锁。既然是进程不安全,那么久有可能在多进程操作的时候发生数据异常。

2) 我们有两个办法能保证进程安全:

使用跨进程组件,也就是ContentProvider,这也是官方推荐的做法。通过ContentProvider对多进程进行了处理,使得不同进程都是通过ContentProvider访问SharedPreferences。
加文件锁,由于SharedPreferences的本质是读写文件,所以我们对文件加锁,就能保证进程安全了。