@Override public Map<AccountTypeWithDataSet, AccountType> getUsableInvitableAccountTypes() { ensureAccountsLoaded(); // Since this method is not thread-safe, it's possible for multiple threads to encounter // the situation where (1) the cache has not been initialized yet or // (2) an async task to refresh the account type list in the cache has already been // started. Hence we use {@link AtomicBoolean}s and return cached values immediately // while we compute the actual result in the background. We use this approach instead of // using "synchronized" because computing the account type list involves a DB read, and // can potentially cause a deadlock situation if this method is called from code which // holds the DB lock. The trade-off of potentially having an incorrect list of invitable // account types for a short period of time seems more manageable than enforcing the // context in which this method is called. // Computing the list of usable invitable account types is done on the fly as requested. // If this method has never been called before, then block until the list has been computed. if (!mInvitablesCacheIsInitialized.get()) { mInvitableAccountTypeCache.setCachedValue(findUsableInvitableAccountTypes(mContext)); mInvitablesCacheIsInitialized.set(true); } else { // Otherwise, there is a value in the cache. If the value has expired and // an async task has not already been started by another thread, then kick off a new // async task to compute the list. if (mInvitableAccountTypeCache.isExpired() && mInvitablesTaskIsRunning.compareAndSet(false, true)) { new FindInvitablesTask().execute(); } } return mInvitableAccountTypeCache.getCachedValue(); }