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

в 8:13, , рубрики: concurrency, java, Servlet, Блог компании Отус, Программирование

Всем привет!

Мы запускаем седьмой поток курса «Разработчик Java». Больше, чем за год существования этого курса он дорабатывался, оттачивался, добавлялось новое, что выходило за это время. Этот же поток отличается от остальных тем, что мы ввели новую систему ступеней разбив курс на три части и чуть увеличив его общую длительность. Так что теперь не надо будет выбиваться из сил пять месяцев подряд для получения сертификата, а спокойно выбрать периоды по два месяца и пройти обучения. Но это лирика, вернёмся к нашей традиции о разных полезностях предшествующих запуску курса.

Поехали.

1. Обзор

Контейнер Java-сервлетов (или веб-сервер) многопоточен: одновременно может выполняться несколько запросов к одному сервлету. Поэтому при написании сервлета необходимо учитывать конкуренцию.

Как мы уже говорили ранее, создается один и только один экземпляр сервлета, и для каждого нового запроса Servlet Container создает новый поток для выполнения doGet() или doPost() методов сервлета.

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

2. Обзор потоков

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

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

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

Давайте рассмотрим простой пример на 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

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

Автор: MaxRokatansky

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js