简体   繁体   中英

Can't initialize static member in Singleton class in Java - Android

I've Singleton SharedPreferences helper class name SyncPrefs .

public class SyncPrefs {

    public static final String TAG = SyncPrefs.class.getSimpleName();

    private static final String HrEmployeeSyncFinished = "hrEmployeeSyncFinished";

    private SharedPreferences mPrefs;
    private SyncFinishedListener mSyncFinishedListener;

    private static SyncPrefs sSyncPrefs;

    private SyncPrefs(Context context, final Employees employees) {
        mPrefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE);
        mSyncFinishedListener = new SyncFinishedListener() {
            @Override
            public void onSyncFinished() {
                employees.mSyncFinishedListener.onSyncFinished();
            }
        };
        // This call start sync & make isSyncFinished() getting called
        SyncUtils.get(context).requestSync(HrEmployee.AUTHORITY);
    }

    public static SyncPrefs getInstance(Context context, final Employees employees) {
        if (sSyncPrefs == null) {
            sSyncPrefs = new SyncPrefs(context, employees);
            Log.e(TAG, "getInstance(Context, Employees) called");
            Log.e(TAG, "sSyncPrefs initialized at: " + sSyncPrefs);
        }
        return sSyncPrefs;
    }

    public static SyncPrefs getInstance() {
        Log.e(TAG, "getInstance() called");
        Log.e(TAG, "sSyncPrefs is: " + sSyncPrefs);
        return sSyncPrefs;
    }

    private boolean isSyncFinished() {
        boolean isSyncFinished = isHrEmployeeSyncFinished();
        // isSyncFinished = true;
        Log.e(TAG, "isSyncFinished is :" + isSyncFinished);
        if (isSyncFinished) {
            try {
                setHrEmployeeSyncFinished(false);
                mSyncFinishedListener.onSyncFinished();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return isSyncFinished;
    }

    private boolean isHrEmployeeSyncFinished() {
        return mPrefs.getBoolean(HrEmployeeSyncFinished, false);
    }

    public SyncPrefs setHrEmployeeSyncFinished(boolean hrEmployeeSyncFinished) {
        mPrefs.edit().putBoolean(HrEmployeeSyncFinished, hrEmployeeSyncFinished).apply();
        if (hrEmployeeSyncFinished) {
            isSyncFinished();
        }
        return this;
    }
}

The above code should run fine. but, somehow I can't initialize the static member sSyncPrefs . I've coonfirmed that sSyncPrefs is getting initialized. but, when I called getInstance() it always returns null .

Here's some Logs

E/SyncPrefs: getInstance(Context, Employees) called
// Look here, it has memory address
E/SyncPrefs: sSyncPrefs initialized at: com.odoo.addons.employees.utils.SyncPrefs@7d0e0a5
E/HrEmployee: onSyncStarted
E/HrEmployee: onSyncFinished
E/SyncPrefs: getInstance() called
// now, where the memory address gone?
E/SyncPrefs: sSyncPrefs is: null

I don't know why this things happen. Any Ideas, Answers or suggestions will be appreciated. Thanks in advanced.

I strongly recommend that you stop using a singleton here. As your question demonstrates, singletons are not easy to implement correctly, especially if you are initializing them lazily.

However, I believe that the reason your code isn't working here is facing a basic memory visibility issue: there is no guarantee that multiple threads see the most recent value for non-volatile variables.

(I am assuming that you don't simply have another bit of code that you didn't share, where you make another assignment of sSyncPrefs to null ).

To implement this "correctly" (and I use the term loosely, because I don't think that a singleton is appropriate), you would need to use double-checked locking :

  1. Make the sSyncPrefs variable volatile :

     private static volatile SyncPrefs sSyncPrefs; 

    This ensures that updates to sSyncPrefs are not cached by threads, and that the value is always read from main memory.

  2. Use synchronization when you check for nullity:

     public static SyncPrefs getInstance(Context context, final Employees employees) { if (sSyncPrefs == null) { synchronized (SyncPrefs.class) { if (sSyncPrefs == null) { sSyncPrefs = new SyncPrefs(context, employees); Log.e(TAG, "getInstance(Context, Employees) called"); Log.e(TAG, "sSyncPrefs initialized at: " + sSyncPrefs); } } } return sSyncPrefs; } 

    The first null check allows you to skip the synchronization once the variable has been initialized; if it is found to be null, the synchronization ensures that no other thread updates the value at the same time.

  3. You also need to use double-checked locking in the no-args getInstance() method to add a check that the variable has actually been initialized.

     public static SyncPrefs getInstance() { if (sSyncPrefs == null) { synchronized (SyncPrefs.class) { if (sSyncPrefs == null) { throw new IllegalStateException("getInstance(Context, Employees) was not called first!"); } } } return sSyncPrefs; } 

您有2个getInstance() ,除非您之前调用了另一个,否则其中一个将始终返回null

You need to set the Static in the constructor so when the singleton is initialised then it sets the TAG, The code you list doesn't have the method you are calling either. SyncPrefs is a singleton so there should be no public constructor and the constructor should either be checked on the getInstance or within a static block (the Spring way of doing it)

    public static class SyncPrefs {

     private static SyncPrefs instance;
     private static final String TAG;

     private SyncPrefs() {
         TAG = "Something";
    }

     public static SyncPrefs getInstance() {
        if(instance == null)
        { 
             instance = new SyncPrefs();
        }
        return instance;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM