Конкуренция в сервлетах

http://www.wideskills.com/servlets/concurrency-in-servlets
  • Перевод
Всем привет!

Мы запускаем седьмой поток курса «Разработчик 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(). В результате возможно, что:

  1. Thread1 считывает значение счетчика, равное 10
  2. Отображает Inital Counter = 10 и собирается инкрементировать
  3. Перед тем как Thread1 инкрементирует счетчик, Thread2 также инкрементирует счетчик, изменяя значение счетчика на 11
  4. В итоге у Thread1 значение счетчика 10, которое уже устарело

Этот сценарий возможен в многопоточной среде, такой как сервлеты, потому, что переменные экземпляра разделяются всеми потоками, запущенными в одном экземпляре.

3. Пишем потокобезопасные сервлеты


Надеюсь, в этом разделе вы поймете проблемы, которые я пытаюсь подчеркнуть. Если у вас есть хоть малейшие сомнения, прочитайте пункт 2 ещё раз.

Есть некоторые моменты, которые мы должны учитывать при написании сервлетов.

  1. Service(), doGet(), doPost() или, в более общем виде, методы doXXX() не должны обновлять или изменять переменные экземпляра, поскольку переменные экземпляра разделяются всеми потоками одного и того же экземпляра.
  2. Если есть необходимость модификации переменной экземпляра, то сделайте это в синхронизированном блоке.
  3. Оба вышеперечисленных правила применимы и для статических переменных также потому, что они также общие.
  4. Локальные переменные всегда являются потокобезопасными.
  5. Объекты запроса и ответа являются потокобезопасными для использования, поскольку для каждого запроса в ваш сервлет создается новый экземпляр и, следовательно, для каждого потока, выполняемого в вашем сервлете.

Ниже приведены два подхода к обеспечению потокобезопасности:

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

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

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

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. Заключение


Мы должны быть очень осторожны при написании сервлетов, поскольку «по умолчанию сервлеты не являются потокобезопасными»

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

Если вы следуете правилам выше, и в следующий раз кто-то спросит вас: «Является ли сервлет потокобезопасным?» — ответьте уверенно: «По умолчанию они не являются, но «Мои сервлеты» являются потокобезопасными».

THE END

Как всегда ждём ваши вопросы, предложения и прочее тут или можно задать их Сергею Петрелевичу на Открытом уроке посвящённому многопоточности.

Отус

207,00

Профессиональные онлайн-курсы для разработчиков

Поделиться публикацией
Комментарии 9
    +3

    И для этого нужна статья?
    Жду на хабре статей "как завязать шнурок, что бы не споткнуться".
    По крайней мере это будет оригинальней (меньше встречается в поиске) чем для банальность про многопоточность и сервлеты.


    Жду возмущенных криков "для для нас это полезно… мы первый раз об этом слышим.."

      –1
      Для тех кому это было неизвестно?
      Как ни странно, но сообщество не так уж однородно и далеко не все являются миддлами и сеньорами с 10+ лет опыта за плечами. Поэтому стараемся находить интересное и для тех кто поопытнее, и для тех кто ещё только начинает свой путь. Звиняйте коли что не так.
        0
        То есть Вы дошли до этого только через 10 лет опыта? Кажется, что статья написана не для сообщества, а для того, чтобы лишний раз попиариться.
          0
          Есть такое слово, как «утрирование» — надеюсь не надо его пояснять, да? :) Я, например, десять лет работал совсем в другой теме и, соответственно, через десять лет это было бы для меня открытием. В частности, могу сказать, что у нас приходят учиться достаточно много людей из смежных языков или областей и точно так же это для них будет небезынтересно.

          Если бы мы хотели только пиариться — то не заморачивались бы с поиском интересных вещей.
            +2
            Судя по статье, вы и не «заморачиваетесь»
              0
              И открытые уроки тоже без заморочек делаем, и статьи ищем через рандомайзер, ага (табличка «Сарказм). Может вон как раз сегодня на джавовский ОУ зайти посмотреть, как оно происходит у нас или посмотреть по спрингу.

      0
      Это называется «конкуррентность».
        +4
        Хотелось бы, чтобы в данной статье по крайней мере были косвенно затронуты не только такие приемы многопоточного программирования, как блоки синхронизации и имплементация deprecated интерфейса SingleThreadModel, но и потокобезопасные коллекции из пакета java.util.concurrent.* типа ArrayBlockingQueue и ConcurrentHashMap, атомарные классы из java.concurrent.atomic и т.д.
        ИМХО, тогда бы это было полезно и начинающим, и в качестве легкого рефреша — продвинутым.
          –1
          Оки. Сегодня-завтра поищем материал, постараемся до старта курса сделать.

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое