Все методы сервлета service должны завершаться к моменту завершения работы сервлета. Сервер пытается обеспечить это, вызывая метод destroy либо после того, как все сервис-запросы будут возвращены, либо по завершению установленного сервером времени ожидания, что быстрее произойдет. Если Ваш сервлет использует операции, занимающие длительное время выполнения (это операции, выполнение которых превышает время ожидания сервера), процесс может быть не завершен в момент, когда сервер вызовет метод destroy. И вы должны обеспечить, чтобы все потоки управляющие клиентскими запросами, были завершены. В этом разделе обсуждается, как это реализовать.

Если у Вашего сервлета существуют потенциальные "долгоиграющие" сервисные запросы придерживайтесь следующих правил:

  • Следите за тем как много потоков запущено методом service.
  • Обеспечьте чистое завершение метода destroy, уведомляя "долгоиграющие" процессы о закрытии и ждите их завершения.
  • Обеспечить "долгоиграющие" процессы возможностью периодической проверки о закрытии сервлета, и в случае такой необходимости: останавливать работу, очищаться и возвращать значения.

Отслеживание сервисных запросов

За слежением за сервисными запросами, включите код в ваш сервлет, который будет считать количество запущенных сервис методов. Этот код должен иметь доступные методы прибавления, вычитания и возвращать их значения. Потому как разные потоки будут обращаться к этим методам, и метод destroy будет ждать, пока их количество не станет равным нулю, доступ к этим методам должен быть синхронизированным. Объект lock с модификатором private это тот объект, с помощью которого производится синхронизация. Например:

public ShutdownExample extends HttpServlet {
    private int serviceCounter = 0;
    private Object lock = new Object();
    ...
    //Методы доступа к стетчику
    protected void enteringServiceMethod() {
        synchronized(lock) {
            serviceCounter++;
        }
    }
    protected void leavingServiceMethod() {
        synchronized(lock) {
            serviceCounter--;
            if (serviceCounter == 0 && isShuttingDown()) {
                notifyAll();
            }
        }
    }
    protected int numServices() {
        synchronized(lock) {
            return serviceCounter;
        }
    }
}

Метод service должен вызывать метод увеличения счетчика каждый раз, когда метод начинает свое выполнение и вызывать метод уменьшения значения счетчика каждый раз, когда он возвращает значение. Вот один из вариантов того, как мог бы подкласс класса HttpServlet переопределить метод service. Переопределенный метод должен вызывать метод super.service для того, чтобы сохранить оригинальную функциональность метода HttpServlet.service.

protected void service(HttpServletRequest req, HttpServletResponse resp) 
        throws ServletException, IOException {
    enteringServiceMethod();
    try {
        super.service(req, resp);
    } finally {
        leavingServiceMethod();
    }
}

Обеспечение "чистого" завершения

Чтобы обеспечить "чистое" завершение, уничтожение не должно разрушать никакие разделяемые ресурсы до тех пор, пока не завершатся все сервис-процессы. Один из способов реализации этого, проверка счетчика выполняемых сервисов. Другой — заключается в оповещении "долгоиграющего" методов, что пора завершать работу. Это выполняет другая часть кода приведенная ниже. Например:

public ShutdownExample extends HttpServlet {
    private boolean shuttingDown;
    ...
    //Методы доступа к процессам завершения
    protected void setShuttingDown(boolean flag) {
        shuttingDown = flag;
    }
    protected boolean isShuttingDown() {
        return shuttingDown;
    }
}

Ниже приведен пример метода destroy использующего вышеуказанные методы для обеспечения "чистого" завершения:

public void destroy() {
    synchronized(lock) {
        // Проверка на наличие ваполняемых сервисов,
        // и если таковые существуют, останавливаем их.
        if (numServices() > 0) {
            setShuttingDown(true);
        }
        // Ждем завершения всех сервисов.
        while(numServices() > 0) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
}

Разработка аккуратных "долгоиграющих" методов

Последняя деталь в обеспечении "чистого" завершения организовать аккуратное исполнение "долгоиграющих" методов. Методы, которые могут выполняться длительное время должны проверять значения методов предупреждающих о наступлении завершения, и при необходимости прерывать работу. Например:

public void doPost(...) {
    ...
    for(i = 0; ((i < lotsOfStuffToDo) && !isShuttingDown()); i++) {
        try { 
             partOfLongRunningOperation(i);
        } catch (InterruptedException e) {
		}
    }
}