March
2009
Сколько нужно потоков для обработки пользовательских запросов или крайности при использовании Singleton
Posted in: Java Web Design, J2EE, Java technologies, J2SE, Паттерны проектирования |
Более трех лет назад, когда я только перешел на Java, написал статью об использовании паттерна Singleton. Статья доступна по этой ссылке - Паттерн Singleton. Не смотря на прошествие значительного промежутка времени, статью до сих пор читают и комментируют. Меня заинтересовала одна ссылка из комментариев, в которой довольно неплохо рассказываются нюансы реализации паттерна Singleton.
В статье говорится о том, что следующая реализация с lazy инициализацией является не оптимальной при достаточно частом обращении к getInstance():
public final class Singleton {
private static Singleton _instance = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (_instance == null)
_instance = new Singleton();
return _instance;
}
}
Давайте попробуем определить, что же такое “достаточно часто”. Так же в статье я хочу показать Вам одну достаточно смешную реализацию Singleton, идею которой рассказал мне мой друг.
Реализацию паттерна Singleton, которую я привел выше, ругают за то, что при большом количестве потоков много потоков будут висеть на мониторе и ждать своего часа.
Для приблизительной оценки слова “много” я предлагаю Вам такой програмный код (импорт классов опущен):
public class TestServlet extends HttpServlet {
private AtomicInteger counter = new AtomicInteger();
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
counter.incrementAndGet();
try { Thread.sleep(100); }
catch (InterruptedException ignore) { }
response.setContentType("text/html; charset=UTF-8");
response.getWriter().print("<html></html>");
System.out.println(counter.get());
counter.decrementAndGet();
}
public static void main(String[] args) throws Exception {
Server server = new Server(8888);
BoundedThreadPool threadPool = new BoundedThreadPool();
threadPool.setMaxThreads(128);
server.setThreadPool(threadPool);
Context context = new Context();
context.setContextPath("/");
context.addServlet(new ServletHolder(new TestServlet()), "/");
new Thread(new Runnable() {
public void run() {
try { Thread.sleep(1000); }
catch (InterruptedException ignore) { }
for (int i = 0; i < 500; i++) {
UserEmulator userEmulator = new UserEmulator();
userEmulator.setDaemon(true);
userEmulator.start();
}
}
}).start();
server.addHandler(context);
server.start();
}
public static class UserEmulator extends Thread {
public void run() {
while (true) {
try {
URL url = new URL("http://localhost:8888/");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.getContent();
connection.disconnect();
Thread.sleep(10000 + new Random().nextInt(5000));
} catch (Exception e) { }
}
}
}
}
Итак, мы имеем сервлет (servlet), который обрабатывает пользовательский запрос за время приблизительно равное 100 миллисекунд. Это достаточно адекватное время, которое годится для подавляющего большинства серьезных веб-проектов. Если Вам это время кажется не достаточным, можете его увеличить. Сервлет содержит поле counter, которое будет отображать количество параллельных запросов пользователей.
Кроме этого, мы имеем класс UserEmulator, который эмулирует работу пользователя на веб-ресурсе. Пользователи у меня получились слишком активные - выполняют одно действие в 10-15 секнуд. Но ничего страшного, это даже наоборот хорошо, - никто не будет говорить, что я упрощаю себе задачу.
Сервлет я запускаю под Jetty (http://www.mortbay.org/jetty/) - это очень хороший веб-сервер с сервлет конейнером, который, в отличии от Tomcat, легко интегрируется в любое приложение без всяких конфигурационных файлов.
После запуска веб-сервера создаются 500 пользовательских потоков (thread), которые периодически заходят на главную станицу нашего портала localhost:8888 :).
Число реально параллельных соединений на стороне веб-сервера выводится в консоль. После того, как пройдет некоторое время и работа программы нормализуется, мы увидим, что для обработки запросов от 500 пользователей требует не больше 15 параллельных потоков. То есть 100 потоков обрабатывают 3-5 тысячи активных пользователей одновременно. Лично мне известно мало приложений (и веб и не веб), на которых одновременно может активно работать 3-5 тысяч пользователей.
Теперь, возвращаясь к паттерну Singleton, а точнее к его реализации, я смело заявляю, что усложнять програмный код буду только в крайних случаях. Что и Вам советую.
Да кстати, вот реализация, на которую предлагают заменить первоначальный вариант:
public final class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
Опять же, данный код не защищает от того, что потоки будут висеть на мониторе при создании экземпляра класса Singleton. Единственное преимущество - после конструирования объекта потоки будут получать доступ к инстансу Singleton без синхронизации. Но и тут не все - Allen Holub заметил, что использование volatile может привести к потерям производительности на мультипроцессорных системах.
Теперь обещанная смешная, но тем не менее заслуживающая внимания, реализация паттерна Singleton, идею которой предложил мне мой друг:
public final class Singleton {
private static volatile Strategy strategy = new CreateAndReturnStrategy();
private static Singleton instance;
private static interface Strategy {
Singleton getInstance();
}
private static class ReturnStrategy implements Strategy {
public final Singleton getInstance() {
return instance;
}
}
private static class CreateAndReturnStrategy implements Strategy {
public final Singleton getInstance() {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
strategy = new ReturnStrategy();
}
}
return instance;
}
}
private Singleton() {}
public static Singleton getInstance() {
return strategy.getInstance();
}
}
По идее это будет даже быстрее работать, чем проверка с помощью оператора if, так как тут используется прямолиенйная диспетчеризация вызовов.
Желаю Всем удачи.
19 Comments »
RSS feed for comments on this post.
Твои примеры косячные. У ibm были отличные статьи про то, как реализировать паттерн Одиночка на java
Если смысл комментария не в том, чтобы оставить ссылку, то прошу объяснить косячность моего примера.
public final class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
Убери synchronized в обьявлении метода.
Да, спасибо, опечатался. Не понял только зачем ссылка на DCL? Проблема с out-of-order записью в примере решается модификатором volatile.
Да и смысл статьи не в этом.
что-то не видно смысла в смешной реализации: теряется “ленивость” паттерна - instance будет создаваться уже при старте jvm, всё остальное так понимаю - просто оригинальная пляска с многопоточностью.
Не согласен.
Инстанс самого синглтона будет создан только во время первого вызова метода getInstance().
>что-то не видно смысла в смешной реализации…
в таком методе тоже ленивая инициализация:
class Singleton{
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
так как класс загружается при первом обращении к нему. и следовательно “смешная реализация”, с идеологией атомарной загрузки вложенного класса, просто избыточна и не нужна.
На счет:
private static Singleton instance = new Singleton();
Если немного усложнить пример и добавить какую-то константу в класс или еще что-то, то ленивость теряется. Идея как раз в том, чтобы загрузка класса не влекла за собой создание инстанса.
Спасибо, действительно интересно)
Ещё один вариант:
public class Singleton {
private static class InstanceHolder {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return InstanceHolder.instance;
}
}
Ну а вообще синглтоны зло, да.
Так это-ж Jetty co своим NIO… а Tomcat то 5000 потоков создаст(нет не создаст
в реальности) так причом тут сингельтон…
А если говорить о сингельтонах то с приведёнными выше примерами есть несколько проблем такие как -
AccessibleObject.setAccessible и возможно проблема сериализации…
Следует просто писать
public enum Singleton { //Реальный сингельтон
INSTANCE;
}
Удачи
А такой вариант ( без volatile ) будет работать? public final class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
return innerGetInstance();
} else
return instance;
}
private static synchronized Singleton innerGetInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
И можно поподробнее про public enum Singleton { //Реальный сингельтон
INSTANCE;
} ( куда писать реализацию класса и когда он будет создаваться? )
Очень уж для меня все это сложно пока выглядит. Почему на программиста не пошел учиться, жалею теперь. А самому, по книжкам, усидчивости не хватает заниматься. Тяжело без наставника.
@cleam предложил лучший вариант*(только как-то робко и final забыл).
*потому что:
- он threadsafe(jvm гарантирует - только один поток загрузит внутренний класс И инициализирует статические поля)
- он lazy-loading(внутренний класс не загрузится пока к нему не обратишься)
- он прост.
Добрый день вы не подумывали о продаже данного блога, интересно было-бы узнать цену, если можно отпишите на мыло
Lots of people understand a technique of term paper thesis writing, however it does not mean they can accomplish premium qulity research papers, nevertheless a essays online service should aid to accomplish the analysis essay of great quality and improve writing skillfulness of some students.
Спасибо огромное.
Пошел дорабатывать …