Работает ли эта реализация для Lazy Singleton без блокировок Thread Safe в Java?

Это то, что я до сих пор, я иду в правильном направлении? Цель состоит в том, чтобы использовать это в сценариях, где одному потоку требуется доступ к синглтону чаще, чем другим потокам, поэтому желателен код без блокировки, я хотел бы использовать атомарные переменные для практики.

public final class ThreadSafeLazyCompareAndSwapSingleton {

private ThreadSafeLazyCompareAndSwapSingleton(){}

private static volatile ThreadSafeLazyCompareAndSwapSingleton instance;
private final static AtomicBoolean atomicBoolean = new AtomicBoolean(false);

public static ThreadSafeLazyCompareAndSwapSingleton getCASInstance(){
    if (instance==null){
        boolean obs = instance==null;
        while (!atomicBoolean.compareAndSet(true, obs == (instance==null))){
            instance = new ThreadSafeLazyCompareAndSwapSingleton(); 
        }
    }
    return instance;
}

}

3 ответа

В этом случае хорошим ментальным подходом было бы разделение потоков на две категории: те, которые могут создавать экземпляры класса, и те, которые не могут. (Для краткости я сокращу название класса до Singleton). Затем вы должны подумать о том, что должна делать каждая категория потоков:

  • инстанцирующие потоки должны хранить ссылку, которую они создают в instance и верни это
  • все остальные темы должны ждать, пока instance был установлен, а затем вернуть его

Кроме того, нам нужно обеспечить две вещи:

  • То, что между созданием экземпляра и всеми возвратами (в том числе в потоках, не связанных с созданием экземпляра), существует грань между событиями. Это для безопасности потока.
  • Что набор экземпляров потоков имеет ровно один элемент (конечно, при условии, что любой набор не пуст). Это для того, чтобы убедиться, что есть только один экземпляр.

Итак, это наши четыре требования. Теперь мы можем написать код, который их удовлетворяет.

private final AtomicBoolean instantiated = new AtomicBoolean(false);
private static volatile Singleton instance = null;
// volatile ensures the happens-before edge

public static Singleton getInstance() {
    // first things first, let's find out which category this thread is in
    if (instantiated.compareAndSet(false, true) {
        // This is the instantiating thread; the CAS ensures only one thread
        // gets here. Create an instance, store it, and return it.
        Singleton localInstance = new Singleton();
        instance = localInstance;
        return localInstance;
    } else {
        // Non-instantiating thread; wait for there to be an instance, and
        // then return it.
        Singleton localInstance = instance;
        while (localInstance == null) {
            localInstance = instance;
        }
        return localInstance;
    }
}

Теперь давайте убедим себя, что каждое из наших условий выполнено:

  • Инстанцирующий поток создает экземпляр, сохраняет его и возвращает его: это "истинный" блок CAS.
  • Другие потоки ожидают установки экземпляра и затем возвращают его: while петля делает.
  • Между созданием экземпляра и возвратом существует грань "случается до" (HB): для экземпляра потока семантика внутри потока обеспечивает это. Для всех остальных тем volatile Ключевое слово обеспечивает грань HB между записью (в потоке создания экземпляров) и чтением (в этой цепочке)
  • То, что набор экземпляров потоков является ровно одним большим, при условии, что метод когда-либо вызывается: первый поток, ударивший по CAS, вернет true; все остальные вернут ложь.

Итак, все готово.

Общий совет здесь состоит в том, чтобы разбить ваши требования на как можно более конкретные требования. Тогда вы можете обратиться к каждому в отдельности, о чем легче рассуждать.

Э-э, трудно читать с этими встроенными условиями / назначениями. Это также в значительной степени нестандартная идиома, поэтому мне интересно, действительно ли вы хотите ее использовать.

По крайней мере, проблема заключается в том, что он может создать более одного экземпляра (в вашем случае это может быть приемлемо, вы не можете этого избежать, если хотите освободиться от блокировки). Но я думаю, что это также может вернуть более одного экземпляра, что, я думаю, не то, что вы хотите.

Я не уверен, что если вам нужен логический тип, вы также можете использовать AtomicReferenceFieldUpdater непосредственно на instance поле.

Я думаю, что obs не нужен, вы бы создали и вернули новый экземпляр, если можете установить логическое значение, и в противном случае выполните цикл:

if (instance!=null)
  return instance;

if (atomicBoolean.compareAndSet(false, true))
{
  instance = new ThreadSafeLazyCompareAndSwapSingleton();
  return instance;
}
while(instance==null);
return instance;

Но я действительно не думаю, что это хорошая идея, чтобы пойти с этим дополнительным логическим значением.

Примерно так будет безопаснее, но есть и лучшие способы.

public final class ThreadSafeLazyCompareAndSwapSingleton {

    // THE instance.
    private static volatile ThreadSafeLazyCompareAndSwapSingleton instance;
    // Catches just one thread.
    private final static AtomicBoolean trap = new AtomicBoolean(false);

    public static ThreadSafeLazyCompareAndSwapSingleton getInstance() {
        // All threads will spin on this loop until instance has been created.
        while (instance == null) {
            // ONE of the spinning threads will get past the trap. Probably the first one.
            if ( trap.compareAndSet(false, true)) {
                // By definition of CAS only one thread will get here and construct.
                instance = new ThreadSafeLazyCompareAndSwapSingleton();
            }
        }
        // By definition instance can never be null.
        return instance;
    }

    // Dont let anyone but me construct.
    private ThreadSafeLazyCompareAndSwapSingleton() {
    }

}

Обратите внимание, что это не удастся, если во время построения возникнет исключение.

Другие вопросы по тегам