Hola a todos,
Para evitar problemas de concurrencia, ten cuidado en la forma de acceder a las variables que son accedidas de forma concurrente: esto es, el generador de Tokens (que puede ser una simple variable estática tipo AtomicInteger, por ejemplo) y utilizar una variable tipo HashMap (que es Thread-Safe), que relacione los tokens con el estado actual...
Este ejemplo lo acabo de publicar en Tomcat y funcionó sin problemas... espero sea claro.
Servlet:
Código:
package test;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class SrvTarea
*/
public class SrvTarea extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* Variable de control de tokens
*/
private static AtomicInteger numToken = new AtomicInteger(0);
/**
* Hashmap que relaciona el estado de la tarea con el token
*/
private static HashMap<Integer,String> estadoTarea =
new HashMap<Integer,String>();
/**
* Función para poner el estado de tarea de un token
* @param token Token a utilizar
* @param estado Estado a actualizar
*/
public static void ponerEstadoTarea(int token, String estado){
estadoTarea.put(new Integer(token), estado);
}
/**
* Borra una tarea terminada de la tabla de estados
* @param token Token de la tarea a borrar
*/
public static void borrarTareaTerminada(int token){
estadoTarea.remove(new Integer(token));
}
/**
* @see HttpServlet#HttpServlet()
*/
public SrvTarea() {
}
private void procesar(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String token = request.getParameter("token");
// Si el token es distinto de nulo, asumo que se piden los datos de una tarea...
if (token!=null){
PrintWriter out = response.getWriter();
out.println("<html><head>");
//Solicita información de tarea...
String estado = estadoTarea.get(new Integer(token));
if (estado==null){
estado = "TAREA TERMINADA!";
} else {
out.print("<meta http-equiv=\"refresh\" content=\"3\">");
}
out.print("</head><body><strong>Estado actual token '" + token + "':" + estado);
out.print("</body></html>");
out.close();
} else {
// Supongo que inicia una nueva tarea
int tokenNuevo = numToken.addAndGet(1);
TareaLarga nuevaTarea = new TareaLarga(tokenNuevo);
nuevaTarea.start();
response.sendRedirect("SrvTarea?token=" + String.valueOf(tokenNuevo));
}
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
procesar(request, response);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
procesar(request, response);
}
}
Página HTML para mostrar la tarea:
Código HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>PRUEBA DE TAREA LARGA</title>
</head>
<script type="text/javascript" language="javascript">
function enviarTarea(){
var iframe = document.getElementById("iframeTarea");
var boton = document.getElementById("botonIniciar")
iframe.src = "SrvTarea";
boton.disabled = "disabled";
}
</script>
<body>
<h1>PRUEBA DE TAREA</h1>
Presione el botón para iniciar la tarea:
<button id="botonIniciar" name="botonIniciar" value="Iniciar Tarea" onclick="javascript:enviarTarea()" >Iniciar Tarea</button><br />
<iframe id="iframeTarea" width="500px" height="300px"></iframe>
</body>
</html>
Tarea "Larga" (Thread):
Código:
package test;
/**
* Thread que simula una tarea "Larga"
* @author Felipe Reyes Palacio
*/
public class TareaLarga extends Thread{
/**
* Token utilizado por la tarea actual
*/
private int tokenLocal;
/**
* Constructor
* @param tokenLocal Asignación del token local
*/
public TareaLarga(int tokenLocal) {
super();
this.tokenLocal = tokenLocal;
}
/**
* Tarea "Larga". Simulada por un Thread de 40 segundos, que actualiza estado cada 4 segundos.
*/
@Override
public void run() {
for(int i=1;i<=10;i++){
// Actualiza el estado de la tarea actual
SrvTarea.ponerEstadoTarea(tokenLocal, String.valueOf(i*10) + "%");
//Simulación de tarea larga... 5 segundos x 10 = 50 segundos.
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
}
}
// Borra la tarea (indicando que ya fue concluida)
SrvTarea.borrarTareaTerminada(tokenLocal);
}
}
No debería haber problemas de concurrencia. Lo único que vale la pena tener en cuenta es que, como la tabla de estados está en memoria, es vital asegurarse que si la tarea a ejecutar puede lanzar excepciones, la tabla se borre aún si falla la ejecución del thread (para que la tabla no se llene infinitamente de datos, cuando haya excepciones). Eso se maneja fácil con un bloque try-finally.
Exitos.