SharedPreferences和线程安全性

56

查看SharedPreferences文档,它说:

"注意:目前此类不支持在多个进程中使用。稍后将添加此功能。"

所以从本身来看,它似乎不是线程安全的。但是,在commit()和apply()方面做了什么样的保证?

例如:

synchronized(uniqueIdLock){
   uniqueId = sharedPreferences.getInt("UNIQUE_INCREMENTING_ID", 0);
   uniqueId++;
   sharedPreferences.edit().putInt("UNIQUE_INCREMENTING_ID", uniqueId).commit();
}

在这种情况下,uniqueId是否保证始终是唯一的?

如果不是,是否有更好的方法来跟踪应用程序中持久存在的唯一标识符?


3
示例代码同步到非 final 字段并更改它。我怀疑你不想这样做。 - Bloodboiler
1
uniqueIdLock 是什么?它在哪里定义?它是静态的、最终的公共等吗?另外,也许你应该切换接受的答案? - Mr_and_Mrs_D
@Bloodboiler 那种情况下会发生什么? - zundi
4个回答

98

进程和线程是不同的。在Android中,SharedPreferences的实现是线程安全的,但不是进程安全的。通常情况下,您的应用程序将在同一个进程中运行,但是您可以通过在AndroidManifest.xml中进行配置,使服务在单独的进程中运行,而活动在另一个进程中运行。

要验证线程安全性,请参见AOSP中的ContextImpl.java的SharedPreferenceImpl。请注意,在所有你期望有的地方都有synchronized。

private static final class SharedPreferencesImpl implements SharedPreferences {
...
    public String getString(String key, String defValue) {
        synchronized (this) {
            String v = (String)mMap.get(key);
            return v != null ? v : defValue;
        }
   }
...
    public final class EditorImpl implements Editor {
        public Editor putString(String key, String value) {
            synchronized (this) {
                mModified.put(key, value);
                return this;
            }
        }
    ...
    }
}

然而对于你的唯一标识符情况,似乎仍需要同步,因为你不希望在获取和放置之间进行更改。


1
synchronized(this) 调用中的 this 部分是指外部对象还是内部对象?我在 Google Groups 上找到了一个参考,它说锁定在不同的对象上 - 不确定这是否正确。 - Richard Le Mesurier
11
尽管 SharedPreferencesImpl 被认为是线程安全的,并且每个文件使用进程唯一的单例,但重要的是要注意它的使用不是本质上原子性的:当两个编辑器同时修改偏好设置时,最后一个调用 commit()apply() 的人会获胜,根据 SharedPreferences.Editor 文档。 - Piovezan
@Piovezan,也许我们可以通过仅使用一个方法编辑SharedPreference来克服最后一个commit()获胜的问题,但是多次读取SharedPreference或在单个写入时读取可能会引起问题~? - Sami Eltamawy
1
@AbdEl-RahmanEl-Tamawy如果是那样的话,就不会有问题,但我不会把它称为“克服‘最后提交获胜’问题”的方法。 - Piovezan
@AbdEl-RahmanEl-Tamawy 是的,我们有,但我们可能会有不同的偏好来读取每个文件,这并不是最常见的情况。通常你希望从单个文件中读取偏好设置。在读或写时使用单一锁对象(锁)进行同步通常可以解决问题。 - Piovezan
显示剩余9条评论

7
我也有同样的疑问,并找到了这个讨论串,其中说它们不是线程安全的:

Context.getSharedPreferences()Editor.commit()实现时没有在同一个监视器上同步。


我查看了Android 14代码以进行检查,发现它相当复杂。具体来说,SharedPreferencesImpl似乎在读写磁盘时使用不同的锁:

  • enqueueDiskWrite()mWritingToDiskLock上加锁
  • startLoadFromDisk()this上加锁,并启动在SharedPreferencesImpl.this上加锁的线程

我不确定这段代码是否真的安全。


1
引用是正确的:它们不会在同一个监视器上进行同步。另一方面,从代码的简要阅读中,我没有看到为什么它们应该这样做。再次强调,只是快速阅读,但在我注意到的唯一一个访问共享可变状态的地方,它们确实使用了相同的监视器。 - G. Blake Meike

4

我认为这样就可以了。

您可以在同步部分内使用sleep进行测试,并从不同的线程调用它。


4

请注意,SharedPreferences在三星手机上无法正常工作,请参考Android问题

我已经实现了简单的数据库偏好存储,您可以在Github上找到。

祝福您!


1
有人知道这是否仍然是事实吗?或者现在大多数Galaxy S用户都已经升级到一些修复了漏洞的版本了吗?考虑到Galaxy S型号的普及程度,我认为这对于使用SharedPreferences来说是一个很大的警示标志 - 这是正确的吗? - skrebbel
今天不是问题。 - Kevin Lee
在那个 Android 问题的线程中,据报道该问题已在 GINGERBREAD.XXJVK 中得到修复。 - jk7

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接