Singleton с изменчивым в java

class MyClass
{
      private static volatile Resource resource;

      public static Resource getInstance()
      {
            if(resource == null)
                  resource = new Resource();
            return resource;
      }
 }

Здесь мое сомнение связано с java concurrency на практике, если вы используете летучую, безопасную публикацию (например, как только ссылка видна другому потоку, данные также доступны). Могу ли я использовать его здесь? Но если это правильно, то предположим, что thread1 теперь проверяет "ресурс", и он null, поэтому он начинает создавать объект. В то время как thread1 создает объект objet другой, т.е. thread2 приходит и начинает проверять значение "resource", а thread2 считает его нулевым (предполагая, что для создания ресурса "ресурс" требуется некоторое значительное количество времени, и поскольку thread1 еще не завершил создание, безопасная публикация не была, следовательно, недоступна для thread2), тогда он также начнет создавать объект? если да, то инвариант класса ломается. Я прав? Пожалуйста, помогите мне в понимании этого особого использования волатильности здесь.

Ответ 1

volatile решает одну проблему, которая является проблемой видимости. Если вы пишете одну переменную , объявляемую volatile, тогда это значение будет видимым для другого потока немедленно. Как мы все знаем, у нас есть разный уровень кэша в os L1, L2, L3, и если мы пишем переменную в одном потоке, то не гарантируется быть видимым для других, поэтому, если мы будем использовать volatile, она будет записываться в прямую память и будет видна другим. Но волатильность не решает проблему атомарности, т.е. int a; a++; небезопасна. AS существует три связанные с ним машинные инструкции.

Ответ 2

Вы правы, несколько потоков могут попытаться создать объект Resource. Volatile просто гарантирует, что если один поток обновит ссылку, все остальные потоки будут видеть новую ссылку, а не некоторую кешированную ссылку. Это медленнее, но безопаснее.

Если вам нужен только один загружаемый ресурс, вам нужно сделать что-то вроде этого:

class MyClass
{
      private static volatile Resource resource;
      private static final Object LOCK = new Object();

      public static Resource getInstance()
      {
            if(resource == null) { 
                synchronized(LOCK) { // Add a synch block
                    if(resource == null) { // verify some other synch block didn't
                                           // write a resource yet...
                        resource = new Resource();
                    }
                }
            }
            return resource;
      }
 }

Ответ 3

Я знаю, что вы не спрашиваете о лучших решениях, но это определенно стоит, если вы ищете ленивое решение синглтон.

Используйте частный статический класс для загрузки singleton. Класс не загружается до вызова и поэтому ссылка не загружается до класса. Загрузка классов по реализации является потокобезопасной, и вы также несете очень небольшие накладные расходы (в случае, если вы выполняете повторяющиеся летучие нагрузки [которые все еще могут быть дешевыми], это разрешение всегда нормальных нагрузок после первоначальной конструкции).

class MyClass {
    public static Resource getInstance() {
        return ResourceLoader.RESOURCE;
    }

    private static final class ResourceLoader {
        private static final Resource RESOURCE = new Resource();
    }
}

Ответ 5

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

В соответствии с tutorial

Reads and writes are atomic for all variables declared volatile

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

Ответ 6

При применении к полю Java volatile гарантирует, что:

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

  • (В Java 5 или более поздней версии) Неустойчивые чтения и записи устанавливают происходит до отношений, подобно приобретению и выпуску мьютекс.

Подробнее информация.

Ответ 7

Вы правы, в этом случае Ресурс может быть сконструирован дважды из-за расы, которую вы описываете. Если вы хотите реализовать синглтон (без явной блокировки) в Java 5+, используйте одноэлементное перечисление, как описано в ответах на Что представляет собой эффективный способ реализации одноэлементного шаблона в Java?.

Ответ 8

прежде всего, имея такой синглтон, вы, по сути, создаете глобальный объект, который является плохой практикой. Я полагаю, вы используете Enums вместо этого.

Ответ 9

Вот мое предложение добавить летучие и синхронизированные вместе.

Примечание: нам еще нужно выполнить двойную проверку.

public class MySingleton {
    private static volatile MySingleton instance;
    private MySingleton() {}

    synchronized private static void newInstance() {
        if(instance == null) {
            instance = new MySingleton();
        }
    }

    public static MySingleton get() {
        if(instance == null) {
            newInstance();
        }
        return instance;
    }
}