56

Looking at the SharedPreferences docs it says:

"Note: currently this class does not support use across multiple processes. This will be added later."

So in and of itself it doesn't appear to be Thread Safe. However, what kind of guarantees are made in regards to commit() and apply()?

For example:

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

Would it be guaranteed that the uniqueId was always unique in this case?

If not, is there a better way to keep track of a unique id for an application that persists?

cottonBallPaws
  • 21,220
  • 37
  • 123
  • 171

4 Answers4

98

Processes and Threads are different. The SharedPreferences implementation in Android is thread-safe but not process-safe. Normally your app will run all in the same process, but it's possible for you to configure it in the AndroidManifest.xml so, say, the service runs in a separate process than, say, the activity.

To verify the thready safety, see the ContextImpl.java's SharedPreferenceImpl from AOSP. Note there's a synchronized wherever you'd expect there to be one.

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;
            }
        }
    ...
    }
}

However for your case of the unique id it seems you'd still want a synchronized as you don't want it to change between the get and the put.

Kevin TeslaCoil
  • 10,037
  • 1
  • 39
  • 33
  • 1
    does the `this` part of the `synchronized(this)` call refer to the outer object, or to the inner objects? I found reference on google groups that says the lock is on different objects - not sure if that's right or not. – Richard Le Mesurier Aug 27 '12 at 12:25
  • @Richard: You can't tell from the snippet above but casual inspection says the code is thread safe. There are two different locks in there -- one on an instance of SharedPreferencesImpl, the other on an instance of EditorImpl -- but the protect different things: mMap and mModified. – G. Blake Meike Feb 07 '13 at 17:45
  • @G.BlakeMeike: except that `mMap` and `mModified` end up being read & written to the same underlying file, access to which seems to be locking on different monitors. However, I have also only made casual inspection of the code and have not done stress tests. (answer updated) – Richard Le Mesurier Feb 08 '13 at 07:43
  • 11
    Despite `SharedPreferencesImpl` being considered thread-safe and using a process-unique singleton per file, it is important to note that its usage is not inherently atomic: when two editors are modifying preferences at the same time, the last one to call `commit()` or `apply()` wins, as per the [SharedPreferences.Editor](http://developer.android.com/reference/android/content/SharedPreferences.Editor.html) documentation. – Piovezan Sep 10 '13 at 13:26
  • Oh, good lord, I didn't know that, @Piovezan. Why don't they simply implement an atomic set-and-commit method for a single preference? That's the most common case and it's easy to forget the "commit" part. – Melinda Green Jan 13 '14 at 00:00
  • @Piovezan, Maybe we can overcome the problem of last `commit()` wins by editing the `SharedPreference` through only one method, but does multiple reading the `SharedPreference` or reading while single writing may cause problems~? – Sami Eltamawy Feb 25 '14 at 12:41
  • @AbdEl-RahmanEl-Tamawy It makes no difference because two editors will actually share one singleton per file under the hood. Even if you declare only one editor and share it between two or more threads, the problem will occur. – Piovezan Feb 25 '14 at 13:02
  • @Piovezan, What I have read and understood is that when " when two editors are modifying preferences at the same time, the last one to call apply wins". So, If I prevented more than one `Thread` to get the Editor() `instance` at the same time then There will be no problem!! Am I true? – Sami Eltamawy Feb 25 '14 at 13:06
  • 1
    @AbdEl-RahmanEl-Tamawy In that case there will be no problem, but I wouldn't call that _'overcoming the problem of "last commit wins"'_. – Piovezan Feb 25 '14 at 13:13
  • @Piovezan, What If to `commit()` did the same behavior but on Two different `SharedPreferences` files? Will they affect each others, or it is a matter of File Writing issue? When we separate the files will solve the issue? – Sami Eltamawy Feb 25 '14 at 13:19
  • @AbdEl-RahmanEl-Tamawy Yes we do but we would have different preferences to read from each file which is not the most common case. You usually want to read preferences from a single file. Synchronizing over a single final object (lock) during a read or write usually solves the issue. – Piovezan Feb 25 '14 at 13:25
  • @Piovezan, The reason I want to write in another `SharedPreference` file is that I am working on an SDK or a Library to be imported in another App. So, I can`t ensure the Synchronization allover the whole APP, So what do you think! – Sami Eltamawy Feb 25 '14 at 13:32
  • @AbdEl-RahmanEl-Tamawy I think you should ask a new question on Stack Overflow providing further details on your scenario and maybe let us know the link in your next comment. :) – Piovezan Feb 25 '14 at 13:38
  • @Piovezan, Thank you. I will give you Vote up for your last question appreciating your help and time – Sami Eltamawy Feb 25 '14 at 13:40
7

I was wondering the same thing - and came across this thread that says they are not thread safe:

The implementations of Context.getSharedPreferences() and Editor.commit () do not synchronize on the same monitor.


I have since looked at the Android 14 code to check, and it is quite involved. Specifically SharedPreferencesImpl seems to use different locks when reading & writing to disk:

  • enqueueDiskWrite() locks on mWritingToDiskLock
  • startLoadFromDisk() locks on this, and launches a thread locking on SharedPreferencesImpl.this

I'm unconvinced that this code really is safe.

legoscia
  • 39,593
  • 22
  • 116
  • 167
Richard Le Mesurier
  • 29,432
  • 22
  • 140
  • 255
  • 1
    The quote is correct: the do not synchronize on the same monitor. On the other hand, I don't see, from casual reading of the code, why they should. Again, just a quick read but in the only place I noticed in which they access shared mutable state, the *do* use the same monitor. – G. Blake Meike Feb 07 '13 at 17:49
4

I think that will do it.

You can test it using sleep inside the synchronized section and call it from different threads

Pedro Loureiro
  • 11,436
  • 2
  • 31
  • 37
4

You should be aware that SharedPreferences are not working on Samsung handsets, have a look at android issue.

I have implemented simple database preferences storage which you can find on github.

Cheers,

Maciej Łopaciński
  • 1,234
  • 12
  • 12
  • 1
    Does anyone know whether this is still the case? Or will most Galaxy S users have upgraded to some bugfixed version by now? Given how popular the Galaxy S models are, I'd call this a big red flag for using SharedPreferences - is that right? – skrebbel Jun 03 '13 at 14:27
  • Don't think it's a problem today – Kevin Lee Apr 14 '16 at 14:20
  • In that android issue thread it was [reported to be fixed](https://code.google.com/p/android/issues/detail?id=14359#c8) in GINGERBREAD.XXJVK – jk7 Mar 17 '17 at 15:36