Каков наилучший способ реализации Singleton, который удовлетворяет следующим требованиям в Java? - PullRequest
0 голосов
/ 07 января 2019

В нашем приложении для Android у нас есть следующие требования

  1. Предварительно загрузите все используемые гарнитуры
  2. Кэширование загруженных гарнитур

До сих пор у нас был простой синглтон, который выглядел так:

@NotThreadSafe
public static void init(Context context) { 

 if (instance == null) {
            instance = new TypefaceHelper(context);
            instance.setAssetManager(context.getAssets());
            instance.setAllTypefaces();
  }
}

Ниже приведены проблемы, с которыми я столкнулся:

  • Не поточно-ориентированный. Обратите внимание, что до сих пор он всегда доступен в Main-Thread (но из разных классов)
  • setAllTypefaces () занимает более 50 мс в среднем, чтобы загрузить 4 шрифта, которые мы используем.

Итак, каждый раз, когда вызывается MyApplication (который расширяет приложение Android), сама инициализация гарнитуры занимала более 50 мс.

В рамках настройки производительности мы решили, что имеет смысл загружать гарнитуры в фоновом режиме. Итак, теперь я хочу загрузить гарнитуры в фоновом режиме, а также сделать поток инициализации безопасным.

Это дизайн, который я придумал

public class TypefaceHelper {

   private static TypefaceHelper instance;

   private volatile Typeface proximaRegular;

   private final Object typefaceLock = new Object();

   private static final Object constructorLock = new Object();



@GuardedBy("constructorLock")
public static void init(Context context) {

    synchronized (constructorLock) {

        if (instance == null) {
            instance = new TypefaceHelper(context);
            instance.setAssetManager(context.getAssets());
            MyApplicationHelper.getInstance().getExecutorService().execute(new Runnable() {
                @Override
                public void run() {
                    instance.setAllTypefaces();  // Set up all Typefaces in background thread
                }
            });

        }
    }

}


@Nullable 
public static TypefaceHelper getInstance() { // Existing code used in 100+ places. Not touched now.
    return instance;
}

// Accessing a typeface. Use Double-checked locking to ensure it works 
public Typeface getProximaRegular() {
    if(proximaRegular==null){ // The variable must be volatile to prevent compiler re-orderings and optimizations
        synchronized (typefaceLock){
            if(proximaRegular==null){
                proximaRegular = Typeface.createFromAsset(assetManager, "fonts/ProximaNova-Regular.otf");
            }
        }
    }
    return proximaRegular;
}


@GuardedBy("typefaceLock")
private void setAllTypefaces(){


    synchronized (typefaceLock) {


        proximaRegular = Typeface.createFromAsset(assetManager, "fonts/ProximaNova-Regular.otf");

        // Initialize 3 other typefaces here 


    }


  }

}

Обратите внимание, что я использую нулевую проверку вне синхронизированного блока в getProxmimaRegular (), потому что к нему обращаются из более чем 100 мест, и я не хочу, чтобы блокировка была получена, если она не равна нулю.

Мои вопросы:

1) Это плохая практика загружать переменные-члены одиночного элемента в фоновом режиме? (В противном случае на производительность сильно влияют)

2) здесь используется отдельная блокировка для init () излишним?

3) Как я могу сделать это лучше, учитывая мои требования?

...