Всем привет!
Мы запускаем седьмой поток курса «Разработчик Java». Больше, чем за год существования этого курса он дорабатывался, оттачивался, добавлялось новое, что выходило за это время. Этот же поток отличается от остальных тем, что мы ввели новую систему ступеней разбив курс на три части и чуть увеличив его общую длительность. Так что теперь не надо будет выбиваться из сил пять месяцев подряд для получения сертификата, а спокойно выбрать периоды по два месяца и пройти обучения. Но это лирика, вернёмся к нашей традиции о разных полезностях предшествующих запуску курса.
Поехали.
Контейнер Java-сервлетов (или веб-сервер) многопоточен: одновременно может выполняться несколько запросов к одному сервлету. Поэтому при написании сервлета необходимо учитывать конкуренцию.
Как мы уже говорили ранее, создается один и только один экземпляр сервлета, и для каждого нового запроса Servlet Container создает новый поток для выполнения doGet() или doPost() методов сервлета.
По умолчанию сервлеты не являются потокобезопасными, программист сам обязан об этом позаботится. В этой главе мы обсудим конкуренцию в сервлетах. Это очень важная концепция, поэтому сосредоточьтесь.
Поток — это легковесный процесс, который имеет свой собственный стек вызовов и пользуется доступом к открытым данным других потоков в одном и том же процессе (общая куча). Каждый поток имеет свой собственный кэш.
Когда мы говорим, что программа многопоточная, мы имеем в виду, что один и тот же экземпляр объекта порождает несколько потоков и обрабатывает единственный элемент кода. Это означает, что через один и тот же блок памяти проходит несколько последовательных потоков управления. Таким образом, несколько потоков выполняют один экземпляр программы и, следовательно, разделяют переменные экземпляра и могут пытаться читать и записывать эти общие переменные.
Давайте рассмотрим простой пример на Java
Теперь мы создаём два потока Thread1 и Thread2 для выполнения
Этот сценарий возможен в многопоточной среде, такой как сервлеты, потому, что переменные экземпляра разделяются всеми потоками, запущенными в одном экземпляре.
Надеюсь, в этом разделе вы поймете проблемы, которые я пытаюсь подчеркнуть. Если у вас есть хоть малейшие сомнения, прочитайте пункт 2 ещё раз.
Есть некоторые моменты, которые мы должны учитывать при написании сервлетов.
Ниже приведены два подхода к обеспечению потокобезопасности:
а) Синхронизируйте блок, в котором вы изменяете экземпляр или статические переменные (см. ниже фрагмент кода).
Мы рекомендуем синхронизировать блок, в котором ваш код изменяет переменные экземпляра вместо синхронизации полного метода ради повышения производительности.
Обратите внимание, что нам нужно сделать блокировку экземпляра сервлета, поскольку мы должны сделать конкретный экземпляр сервлета потокобезопасным.
b) Single Thread Model — внедрите SingleThreadModel интерфейс, чтобы сделать поток однопоточным, что означает, что только один поток будет выполнять метод service() или doXXX() за раз. Однопоточный сервлет медленнее под нагрузкой, потому что новые запросы должны ждать свободного экземпляра, чтобы быть обработанными
Использование SingleThreadModel устарело, т. к. рекомендуется использовать синхронизированные блоки.
Мы должны быть очень осторожны при написании сервлетов, поскольку «по умолчанию сервлеты не являются потокобезопасными»
Если вы следуете правилам выше, и в следующий раз кто-то спросит вас: «Является ли сервлет потокобезопасным?» — ответьте уверенно: «По умолчанию они не являются, но «Мои сервлеты» являются потокобезопасными».
THE END
Как всегда ждём ваши вопросы, предложения и прочее тут или можно задать их Сергею Петрелевичу на Открытом уроке посвящённому многопоточности.
Мы запускаем седьмой поток курса «Разработчик Java». Больше, чем за год существования этого курса он дорабатывался, оттачивался, добавлялось новое, что выходило за это время. Этот же поток отличается от остальных тем, что мы ввели новую систему ступеней разбив курс на три части и чуть увеличив его общую длительность. Так что теперь не надо будет выбиваться из сил пять месяцев подряд для получения сертификата, а спокойно выбрать периоды по два месяца и пройти обучения. Но это лирика, вернёмся к нашей традиции о разных полезностях предшествующих запуску курса.
Поехали.
1. Обзор
Контейнер Java-сервлетов (или веб-сервер) многопоточен: одновременно может выполняться несколько запросов к одному сервлету. Поэтому при написании сервлета необходимо учитывать конкуренцию.
Как мы уже говорили ранее, создается один и только один экземпляр сервлета, и для каждого нового запроса Servlet Container создает новый поток для выполнения doGet() или doPost() методов сервлета.
По умолчанию сервлеты не являются потокобезопасными, программист сам обязан об этом позаботится. В этой главе мы обсудим конкуренцию в сервлетах. Это очень важная концепция, поэтому сосредоточьтесь.
2. Обзор потоков
Поток — это легковесный процесс, который имеет свой собственный стек вызовов и пользуется доступом к открытым данным других потоков в одном и том же процессе (общая куча). Каждый поток имеет свой собственный кэш.
Когда мы говорим, что программа многопоточная, мы имеем в виду, что один и тот же экземпляр объекта порождает несколько потоков и обрабатывает единственный элемент кода. Это означает, что через один и тот же блок памяти проходит несколько последовательных потоков управления. Таким образом, несколько потоков выполняют один экземпляр программы и, следовательно, разделяют переменные экземпляра и могут пытаться читать и записывать эти общие переменные.
Давайте рассмотрим простой пример на Java
public class Counter
{
int counter=10;
public void doSomething()
{
System.out.println(“Inital Counter = ” + counter);
counter ++;
System.out.println(“Post Increment Counter = ” + counter);
}
}
Теперь мы создаём два потока Thread1 и Thread2 для выполнения
doSomething()
. В результате возможно, что:- Thread1 считывает значение счетчика, равное 10
- Отображает Inital Counter = 10 и собирается инкрементировать
- Перед тем как Thread1 инкрементирует счетчик, Thread2 также инкрементирует счетчик, изменяя значение счетчика на 11
- В итоге у Thread1 значение счетчика 10, которое уже устарело
Этот сценарий возможен в многопоточной среде, такой как сервлеты, потому, что переменные экземпляра разделяются всеми потоками, запущенными в одном экземпляре.
3. Пишем потокобезопасные сервлеты
Надеюсь, в этом разделе вы поймете проблемы, которые я пытаюсь подчеркнуть. Если у вас есть хоть малейшие сомнения, прочитайте пункт 2 ещё раз.
Есть некоторые моменты, которые мы должны учитывать при написании сервлетов.
Service()
,doGet()
,doPost()
или, в более общем виде, методыdoXXX()
не должны обновлять или изменять переменные экземпляра, поскольку переменные экземпляра разделяются всеми потоками одного и того же экземпляра.- Если есть необходимость модификации переменной экземпляра, то сделайте это в синхронизированном блоке.
- Оба вышеперечисленных правила применимы и для статических переменных также потому, что они также общие.
- Локальные переменные всегда являются потокобезопасными.
- Объекты запроса и ответа являются потокобезопасными для использования, поскольку для каждого запроса в ваш сервлет создается новый экземпляр и, следовательно, для каждого потока, выполняемого в вашем сервлете.
Ниже приведены два подхода к обеспечению потокобезопасности:
а) Синхронизируйте блок, в котором вы изменяете экземпляр или статические переменные (см. ниже фрагмент кода).
Мы рекомендуем синхронизировать блок, в котором ваш код изменяет переменные экземпляра вместо синхронизации полного метода ради повышения производительности.
Обратите внимание, что нам нужно сделать блокировку экземпляра сервлета, поскольку мы должны сделать конкретный экземпляр сервлета потокобезопасным.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ThreadSafeServlet extends HttpServlet {
@override
public void doGet (HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
int counter;
{
synchronized (this) {
//code in this block is thread-safe so update the instance variable
}
//other processing;
}
b) Single Thread Model — внедрите SingleThreadModel интерфейс, чтобы сделать поток однопоточным, что означает, что только один поток будет выполнять метод service() или doXXX() за раз. Однопоточный сервлет медленнее под нагрузкой, потому что новые запросы должны ждать свободного экземпляра, чтобы быть обработанными
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ThreadSafeServlet extends HttpServlet implements SingleThreadModel {
int counter;
// no need to synchronize as implemented SingleThreadModel
@override
public void doGet (HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
}
Использование SingleThreadModel устарело, т. к. рекомендуется использовать синхронизированные блоки.
4. Заключение
Мы должны быть очень осторожны при написании сервлетов, поскольку «по умолчанию сервлеты не являются потокобезопасными»
- Если ваш сервлет не имеет какой-либо статической или переменной-члена, вам не нужно беспокоиться, и ваш сервлет является потокобезопасным
- Если ваш сервлет просто читает переменную экземпляра, ваш сервлет является потокобезопасным.
- Если вам нужно изменить экземпляр или статические переменные, обновите его в синхронизированном блоке, удерживая блокировку экземпляра
Если вы следуете правилам выше, и в следующий раз кто-то спросит вас: «Является ли сервлет потокобезопасным?» — ответьте уверенно: «По умолчанию они не являются, но «Мои сервлеты» являются потокобезопасными».
THE END
Как всегда ждём ваши вопросы, предложения и прочее тут или можно задать их Сергею Петрелевичу на Открытом уроке посвящённому многопоточности.
Комментарии (9)
saintrude
21.06.2018 15:55+4Хотелось бы, чтобы в данной статье по крайней мере были косвенно затронуты не только такие приемы многопоточного программирования, как блоки синхронизации и имплементация deprecated интерфейса SingleThreadModel, но и потокобезопасные коллекции из пакета java.util.concurrent.* типа ArrayBlockingQueue и ConcurrentHashMap, атомарные классы из java.concurrent.atomic и т.д.
ИМХО, тогда бы это было полезно и начинающим, и в качестве легкого рефреша — продвинутым.MaxRokatansky Автор
21.06.2018 15:56-1Оки. Сегодня-завтра поищем материал, постараемся до старта курса сделать.
mmMike
И для этого нужна статья?
Жду на хабре статей "как завязать шнурок, что бы не споткнуться".
По крайней мере это будет оригинальней (меньше встречается в поиске) чем для банальность про многопоточность и сервлеты.
Жду возмущенных криков "для для нас это полезно… мы первый раз об этом слышим.."
MaxRokatansky Автор
Для тех кому это было неизвестно?
Как ни странно, но сообщество не так уж однородно и далеко не все являются миддлами и сеньорами с 10+ лет опыта за плечами. Поэтому стараемся находить интересное и для тех кто поопытнее, и для тех кто ещё только начинает свой путь. Звиняйте коли что не так.
galvanom
То есть Вы дошли до этого только через 10 лет опыта? Кажется, что статья написана не для сообщества, а для того, чтобы лишний раз попиариться.
MaxRokatansky Автор
Есть такое слово, как «утрирование» — надеюсь не надо его пояснять, да? :) Я, например, десять лет работал совсем в другой теме и, соответственно, через десять лет это было бы для меня открытием. В частности, могу сказать, что у нас приходят учиться достаточно много людей из смежных языков или областей и точно так же это для них будет небезынтересно.
Если бы мы хотели только пиариться — то не заморачивались бы с поиском интересных вещей.
galvanom
Судя по статье, вы и не «заморачиваетесь»
MaxRokatansky Автор
И открытые уроки тоже без заморочек делаем, и статьи ищем через рандомайзер, ага (табличка «Сарказм). Может вон как раз сегодня на джавовский ОУ зайти посмотреть, как оно происходит у нас или посмотреть по спрингу.