Поведение класса Java Singleton в многопоточной среде

1

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

Вопрос 1: Если существует несколько потоков, которым необходимо получить доступ к объекту singleton (например, сконфигурирован как одноэлементный компонент в конфигурации Spring), тогда только один поток может получить доступ, а другие блокируются до тех пор, пока текущая поддерживающая нить не освободит его.

Для меня блокировка здесь имеет смысл, потому что есть только один объект, и все потоки не могут получить тот же объект одновременно.

Поэтому, если я настрою свой DAO как singleton bean, тогда, если несколько пользователей (потоки) попытаются сделать чтение и запись через этот DAO, тогда это фактически не произойдет одновременно, а последовательный y ---> проблема производительности в приложении с интенсивным использованием базы данных.

С другой стороны, большинство DAO являются апатридами (по крайней мере, в моем случае), поэтому вместо того, чтобы настраивать его как Singleton, я могу создавать его там, где мне нужно, и выполнять операции одновременно, но для каждого экземпляра объекта может потребоваться некоторое время и память. Так что это дизайнерское решение.

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

Вопрос 2: Заданный здесь вопрос

Я думаю, что лучшим ответом является S.Lott (я не знаю, как указать ссылку непосредственно на ответ, чтобы скопировать ответ):

Синглтоны решают одну (и только одну) задачу.

Конфликт ресурсов.

Если у вас есть ресурс, который

(1) может иметь только один экземпляр и

(2) вам нужно управлять этим единственным экземпляром,

вам нужен синглтон.

Вот мой вопрос: если выше ответ true--> Это будет причиной того, что регистраторы реализованы как одиночные, потому что у вас есть один файл журнала (единственный ресурс).

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

Я рисую над текстом, потому что я думаю, что если один поток регистрируется, то второй поток не может регистрироваться вообще (заблокирован) из-за конфигурации singleton, там, избегая делать лог файл тарабарщиной.

Правильно ли я понимаю?

  • 0
    В случае многопоточной среды синглтон-поведение будет синхронизировано.
  • 0
    А синхронизированный означает последовательный доступ к ресурсу (здесь объект). Так что не так хорошо для производительности :(
Показать ещё 1 комментарий
Теги:
multithreading

5 ответов

0
Лучший ответ

Что бы ни говорили @james, это правильно. На ваши вопросы:

Вопрос 1. Несколько потоков могут получить доступ к объекту singleton без блокировки, если вы не сделаете метод get, который возвращает объект, синхронизированный. См. Ниже (Несколько потоков могут получить ссылку на объект singleton без блокировки).

public class Singleton {
    private Singleton() {
    }

    private static class SingletonHelper {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

Вопрос 2: Ваше понимание неверно. Если один поток пишет, другой поток должен ждать, это происходит не из-за конфигурации singleton, а потому, что возможно, что объект (файл) может оказаться в несогласованном состоянии; то же самое применимо для любого объекта, который не синхронизирован должным образом.

  • 0
    Да, я действительно перепутал синглтон и синхронизацию. НЕТ причин блокировать потоки, потому что методы НЕ синхронизированы. Таким образом, параллельное выполнение методов в экземпляре синглтона (в моем случае DAO), безусловно, не является проблемой, и, следовательно, в случае объектов без состояния, использующих синглеты, фактически приведет к хорошей производительности, потому что нам не нужно снова и снова создавать один и тот же объект. Что меня смутило, так это то, что несколько потоков могут одновременно ссылаться на один и тот же объект. И я думаю, что это то, что API параллелизма делает и учит нас делать потокобезопасным способом для объектов с состоянием. Не так ли?
  • 1
    Несколько потоков могут одновременно содержать ссылку на один и тот же объект. Если программа работает на одноядерном процессоре, то в любой момент времени может выполняться только один поток, поэтому в любой момент времени только один поток может получить ссылку. Если программа работает на многоядерном процессоре и метод get reference не синхронизирован, то да, возможно, несколько потоков могут получить ссылку на один и тот же объект одновременно.
2

"Singleton" не имеет смысла в языке программирования Java. Это всего лишь шаблон дизайна. Поведение одноэлементного объекта, разделяемого многими потоками, ничем не отличается от поведения любого другого объекта, который разделяется многими потоками.

Это безопасно, если и только если вы сделаете это безопасным.

1

вы связываете шаблон singleton и многопоточное приложение, предполагая, что доступ к объекту singleton должен быть сериализован; это неправда.

Требование состоит в том, чтобы синглтон должен быть потокобезопасным.

Рассмотрим пример DAO в вашем вопросе; предполагая, что каждый вызов является апатридом (т.е. вы не используете переменные в классе, но только в методах), вам не нужно несколько экземпляров одного и того же DAO, в приложении Spring обычно есть один или несколько классов менеджера (обычно вы управлять транзакциями DB этих классов менеджеров с помощью AOP); каждый из них имеет ссылку на один DAO. Каждый раз, когда вызывается объект DAO, он получает соединение БД с источником данных и выполняет требуемую операцию, затем он освобождает соединение с источником данных.

Когда несколько потоков вызывают ваш менеджерский класс, вам необходимо получить/освободить соединение с БД потоком безопасным способом. Обычно Spring скрывает эту сложность, и вам не нужно об этом беспокоиться.

Псевдокод для дао - это что-то вроде

public void doSomeDBOperation(YourObject param) {
    Connection connection=acquireDBConnection();//the connection must be acquired in a thread safe way
    SQLStatement statement=connection.createStatement(yourSQL);
    //do the operation with your param;
    releaseDBConnection(connection);
}

Spring делает sometinh подобным под капотом, он приобретает соединение Db в аспекте AOP, он сохраняет локальную переменную потока, поэтому ее можно использовать несколькими DAO, и вы можете управлять транзакцией по соединению.

Таким образом, один менеджер, один dao и несколько соединений DB - это способ управления многопоточными операциями параллельно (вы сериализуете только аренду/выпуск DBconnection при условии, что используете пул соединений) и выполняете операцию БД без блокировки (на уровне java на db операции уровня могут блокироваться, чтобы гарантировать ограничения целостности).

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

public class MyClass {
    private static final Logger logger=LoggerFactory.get(MyClass.class.getName());
    .....
}

Класс logger и файл, в котором вы регистрируетесь, напрямую не связаны, с Log4J, LogBack и т.д. Вы можете регистрироваться в нескольких файлах с одним вызовом журнала, вы даже можете регистрироваться на то, что не является файлом (например, сокет). Реальные операции записи выполняются приложением, приставки должны быть потокобезопасными и сериализовать доступ к базовому ресурсу, если это необходимо. В вашем приложении вы объявляете несколько логгеров (по одному для каждого класса), поэтому в одном приложении для одного класса есть не один класс журнала.

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

1

Цитата у вас есть правильная, но неполная. Синглтон имеет больше целей, которые, надеюсь, станут ясными позже.

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

class Stateful {
    private String s; // shared resource
    void setS(String s) {
        this.s = s;
    }
}

class Stateless {
    int print(String s) {
        return s.size();
    }
}

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

Требуется больше работы для реализации в режиме реального времени. Опять же, чтобы ответить на этот вопрос, предположим, что существуют две категории реализации: блокировка и неблокирование. Блокировка - как описано (хотя существуют различные типы блокировки). Неблокирование означает, что несколько потоков могут вызывать методы одновременно. Как правило, существует поток, специально предназначенный для неблокирующей реализации для обработки.

Регистраторы - хорошие примеры неблокирующих реализаций. Ниже приведен очень упрощенный код.

class Logger {
    private List<String> msgs = new CopyOnWriteArrayList<>();
    void log(String msg) {
        msgs.add(msg);
    }
    private void process() {...} // used internally by a thread specially for Logger
}

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

  • 0
    Какие существуют виды блокировки?
  • 1
    Mutex, много читателей, один писатель и т. Д.
0

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

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

  • 0
    отредактировал мой первоначальный вопрос (см. зачеркнутый текст) - я думаю, это то, что вы имели в виду. Может быть запущен только один поток ведения журнала, а другие будут заблокированы для ресурса (здесь экземпляр singleton logger).

Ещё вопросы

Сообщество Overcoder
Наверх
Меню