Как избежать снижения производительности при использовании volatile в одноэлементном шаблоне? - PullRequest
3 голосов
/ 10 июля 2019

Скажите код для шаблона Singleton:

class Singleton 
{ 
    private volatile static Singleton obj; 

    private Singleton() {} 

    public static Singleton getInstance() 
    { 
        if (obj == null) 
        { 
            synchronized (Singleton.class) 
            { 
                if (obj==null) 
                    obj = new Singleton(); 
            } 
        } 
        return obj; 
    } 
} 

obj в приведенном выше коде помечен как Volatile, что означает, что всякий раз, когда в коде используется obj, он всегда выбирается из основной памяти вместо использованиякэшированное значение.Таким образом, всякий раз, когда необходимо выполнить if(obj==null), он выбирает obj из основной памяти, хотя его значение было установлено в предыдущем запуске.Это накладные расходы производительности при использовании ключевого слова volatile.Как нам этого избежать?

Ответы [ 4 ]

1 голос
/ 10 июля 2019

Вы серьезно не понимаете, что делает volatile, но, если честно, Интернет и стекопоток, в том числе, просто загрязнены неправильными или неполными ответами по этому поводу. Я также признаю, что я думаю Я хорошо это понимаю, но иногда мне приходится перечитывать некоторые вещи снова.

То, что вы там показали, называется идиомой "двойная проверка блокировки", и это совершенно правильный сценарий использования для создания синглтона. Вопрос в том, действительно ли он вам нужен в вашем случае (другой ответ показал гораздо более простой способ, или вы можете прочитать «enum singleton pattern», если хотите). Немного забавно, что многие люди знают, что volatile нужен для этой идиомы, но не могут точно сказать , почему это необходимо.

DCL выполняет в основном две вещи - обеспечивает атомарность (несколько потоков не могут одновременно войти в синхронизированный блок) и гарантирует, что после создания все потоки увидят этот созданный экземпляр, называемый видимость . В то же время он гарантирует, что синхронизированный блок будет введен один раз , после этого все потоки не должны будут это делать.

Вы могли бы легко сделать это через:

  private Singleton instance;

  public Singleton get() {
    synchronized (this) {
      if (instance == null) {
        instance = new Singleton();
      }
      return instance;
    }
  }

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

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

  private Singleton instance; // no volatile

  public Singleton get() {
    if (instance == null) {  
      synchronized (this) {
        if (instance == null) { 
          instance = new Singleton();
        }
      }
    }
    return instance; 
  }

Все так просто - это сломано . И это не легко объяснить.

  • он поврежден, потому что есть два независимых чтения из instance; JMM позволяет переупорядочивать их; таким образом, полностью действительно, что if (instance == null) не видит ноль; в то время как return instance; видит и возвращает null. Да, это нелогично, но вполне допустимо и доказуемо (я могу написать jcstress тест, чтобы доказать это за 15 минут).

  • второй пункт немного сложнее. Предположим, в вашем синглтоне есть поле, которое вам нужно установить.

Посмотрите на этот пример:

static class Singleton {

    private Object some;

    public Object getSome() {
        return some;
    }

    public void setSome(Object some) {
        this.some = some;
    }
}

И вы пишете такой код, чтобы обеспечить этот синглтон:

private Singleton instance;

public Singleton get() {
    if (instance == null) {  
        synchronized (this) {
            if (instance == null) { 
                instance = new Singleton();
                instance.setSome(new Object());
            }
        }
    }
    return instance; 
}

Поскольку запись в volatile (instance = new Singleton();) происходит за до установки нужного вам поля instance.setSome(new Object());; какой-то поток, который читает этот экземпляр, может увидеть, что instance не равен нулю, но при выполнении instance.getSome() увидит ноль. Правильный способ сделать это (плюс сделать экземпляр volatile):

 public Singleton get() {
    if (instance == null) {
        synchronized (this) {
            if (instance == null) {
                Singleton copy = new Singleton();
                copy.setSome(new Object());
                instance = copy;
            }
        }
    }
    return instance; 
}

Таким образом volatile здесь необходимо для безопасной публикации ; так что опубликованная ссылка «безопасно» видна всем потокам - все ее поля инициализируются. Существуют и другие способы безопасной публикации ссылки, например, final, заданный в конструкторе и т. Д.

Факт жизни: читает дешевле, чем пишет ; вам не важно, что читает volatile под капотом, если ваш код верен; так что не беспокойтесь о «чтениях из основной памяти» (или даже лучше не используйте эту фразу, даже не поняв ее частично).

0 голосов
/ 10 июля 2019

Вы можете использовать Ленивая инициализация со статическим классом Holder

class Singleton 
{ 

    private Singleton() {} 

    private static class LazyLoader{ 
        static final Singleton obj = new Singleton();
    }

    public static Singleton getInstance() 
    { 
        return LazyLoader.obj;
    } 
} 

Здесь важно отметить, что конструктор должен быть отказоустойчивым, иначе загрузчик классов выдаст NoClassDefFoundError

0 голосов
/ 10 июля 2019

Вы должны использовать Enums для реализации Singleton.

Джошуа Блох (Joshua Bloch) предлагает использовать Enum для реализации шаблона проектирования Singleton, потому что Java гарантирует, что любое значение enum создается только один раз в программе Java.Недостаток в том, что тип enum несколько негибок;Например, он не позволяет выполнять отложенную инициализацию.

public enum EnumSingleton {
    INSTANCE;
    int value;
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
}
public class EnumDemo {
    public static void main(String[] args) {
        EnumSingleton singleton = EnumSingleton.INSTANCE;
        System.out.println(singleton.getValue());
        singleton.setValue(2);
        System.out.println(singleton.getValue());
    }
}

В этом сообщении были перечислены другие преимущества использования Enums: java singleton instantiation

0 голосов
/ 10 июля 2019

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

public class Singleton{
    //Initialized when class loading
    private static final Singleton INSTANCE = new Singleton();

    //To avoid creating new instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...