Ejercicio concurrencia en Java

E

Estoy repasando para mi próximo examen y estoy haciendo los ejercicios del examen que suspendí. Este en concreto se me está resistiendo:


Se dispone de una aplicación web de juegos on-line. Para evitar que la aplicación web se sature, hay un límite máximo de partidas simultáneas, definido mediante la constante MAX_PARTIDAS. En la implementación actual, cuando se recibe una petición para crear una nueva partida, se comprueba si ya se ha alcanzado el máximo de partidas actuales. En ese caso, se devuelve un error al cliente. Nos han pedido que modifiquemos la implementación actual para evitar en la medida de lo posible los
errores enviados a los clientes. En concreto, nos piden que en el caso en que el número de partidas sea el máximo, en vez de devolver un error de forma inmediata, el hilo quede bloqueado durante un tiempo a la espera de que una de las partidas actuales finalice. Si no finaliza ninguna partida en el
tiempo indicado, entonces se devolverá el error. Pero en caso de que finalice una partida antes, entonces se devolverá correctamente al cliente.

public class GestorMaxPartidas {
	public static final int MAX_PARTIDAS = 10;

// Atributos necesarios
public GestorMaxPartidas() {
}

// Este método será invocado cuando se quiera usar crear una nueva partida.
// Devolverá true si se puede crear una partida (no se ha alcanzado el
// máximo).
// En caso de que se haya alcanzado el máximo de partidas, se bloqueará
// hasta que
// alguna partida finalice o bien se supere el tiempo de espera
// (waitTimeMillis).
// Si alguna partida finaliza antes de que se supere el tiempo, se devolverá
// true.
// En caso contrario, se devolverá false.
public boolean iniciarNuevaPartida(long waitTimeMillis) {
}

// Este método será invocado cuando una partida previamente iniciada
// finalice.
public void finalizarPartida() {
}
}

Se pide completar la clase anterior: incluyendo los atributos necesarios y añadiendo la implementación adecuada en los métodos y el constructor de forma que se comporte como se ha indicado previamente. No es necesario implementar ninguna clase adicional, únicamente completar la clase GestorMaxPartidas.


Lo he intentado de muchas formas (usando monitores, estructuras de sincronización de alto nivel, estructuras sincronizadas) pero nada, no consigo de ninguna forma "enfocar" el ejercicio. Si a alguien se le ilumina la bombilla estaré agradecido xD

jalamoNNN

Lo que yo haría sería tener un semáforo global inicializado con MAX_PARTIDAS tokens.
De esta manera, cada thread al inicio deberá de hacer un acquire antes de comenzar a ejecutarse y un release al finalizar.
Si no hay tokens disponibles (es decir, se ha llegado al máximo de partidas concurrentes) entonces el thread se bloqueará hasta que un thread finalice y haga el release.

1 respuesta
E

#2 así es, al rato de poner el post se me ocurrió y lo hice. El problema estaba en que no podias bloquearlos de por vida, si no un tiempo, y eso me liaba ;)

public class GestorMaxPartidas {
	public static final int MAX_PARTIDAS = 10;
	public static Semaphore partidas = new Semaphore(MAX_PARTIDAS);

public GestorMaxPartidas() {

}

public boolean iniciarNuevaPartida(long waitTimeMillis) {
	try {
		if (partidas.tryAcquire(waitTimeMillis, TimeUnit.MILLISECONDS)) {
			System.out.println("iniciando partida");
			return true;
		} else {
			System.out.println("partida imposible de iniciar");
			return false;
		}
	} catch (InterruptedException e) {
		return false;
	}
	
}

public void finalizarPartida() {
	partidas.release();
}
}
22 días después
E

Una pregunta... Tengo un ConcurrentHashMap el cual contiene objetos de mi clase Chat. Dichos chats tienen su lista de usuarios, etc...

Entonces quiero recorrer el ConcurrentHashMap con varios threads para así poder hacer los cálculos que me piden más rápidamente. Pero como puedo dividir el ConcurrentHashMap entre varios threads? Un array a secas sabría hacerlo, pero esta estructura cómo se haría? :S

EDIT: yo trabajo sobre una copia, no sobre el original, porque las estadísticas se deben calcular cada 3 segundos y deben ser coherentes entre sí

LLoid

¿Lo que no controlas es acceder a los elementos del hashmap o crear y usar varios hilos que acceden a ellos simultáneamente? Entiendo que es lo segundo, en cuyo caso tienes 2 opciones, o bien que tu clase implemente la interfaz runnable y te crees y ejecutes varias instancias, o que tu clase herede de Thread.

Si el problema es el acceso al hashmap, generalmente se accede mediante la key con la que hayas hecho el .put (un string). Puedes iterar el hashmap entero tal que así:

Map<String, Integer> map = new HashMap<String, Integer>();

for (String key: map.keySet()) {
    System.out.println("key : " + key)
    System.out.println("value : " + map.get(key));
}
1 respuesta
E

#5 es lo segundo. Pero yo no quiero que 2 recorran toda la estructura, que eso se hacerlo, si no dividir la estructura en 2, 3, 4, lo que sea y que 2, 3, 4,lo que sea de threads, cada uno recorra una parte

1 respuesta
jalamoNNN

#6 así una solución un poco chapucera que se me ocurre de primeras sería coger el keySet() de tu objeto ConcurrentHashMap y después el Iterator de este objeto que has cogido. Cada thread tendría una referencia a este Iterator y tendrías que recorrerte el iterador con la forma de siempre, if (iterator.hasNext()) iterator.next() etc etc. La única pega es que la ejecución de este if y su cuerpo tendrían que ser en exclusión mútua, porque creo que el iterator que te devuelve no garantiza la concurrencia. Pero vamos, que eso con un mutex se arregla.

1 respuesta
E

#7 pero keySet exactamente que devuelve? Devuelve un Set de 16 en 16? (que es lo del concurrencyLevel) o me devuelve todos?

1 respuesta
jalamoNNN

#8 lo mejor es que te leas la documentación que te proporciona Java, que yo creo que es bastante completa:
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html#keySet--

1 respuesta
Lecherito

Java8 y parallel streams!

1 respuesta
E

#9 #10 la verdad es que estoy algo perdido con esto xD

EDIT: #9 ya lo logre! estaba algo perdido, pero gracias por tu idea ;)

EDIT2: si lo meto en un mutex como tú dices poca hostia, de nada me sirve la concurrencia xD eso está mal xD

1 respuesta
jalamoNNN

#11 lo que debes de meter en un mutex es el acceso al recurso compartido, que es tu objeto ConcurrentHashMap. La idea era primero hacer en sección crítica if(iterator.hasNext()) variableLocal = iterator.next() y después el procesado fuera de sección crítica. Es decir, cuando un thread tiene la referencia a un valor de tu ConcurrentHashMap ya sale de sección crítica (libera el recurso para que otro thread pueda acceder a él) y pasa a hacer sus cálculos. Supongo que el tamaño de tus cálculos será bastante más costoso en tiempo que el bloqueo que te puede suponer el acceso en exclusión mútua, porque sino no tendría sentido dividir el trabajo en varios threads. Por lo tanto el hecho de tener una sección crítica tan pequeña te penalizará muy poco en el tiempo de cómputo.
No obstante no sé cómo está hecho tu programa. Aquí tienes otra manera de hacerlo en el caso de que tengas sólo un hilo principal antes de querer hacer tus cálculos:
http://stackoverflow.com/questions/9455280/java-iterator-concurrency

1 respuesta
E

#12 aaaa joder, es que yo tenía un while y tu lo has hecho con un if, por eso tú puedes entrar y salir con el mutex, yo con el while no podía, solo entraba una vez y luego ya desbloqueaba infinitas veces...

Como podría recorrerme toda la estructura sin el while y hacerlo como tú dices? Pero vamos, al final vi ese post de stackoverflow y lo hice con un executor y demás (tampoco creo que me lo ponga como mal...)

Mil gracias por tu ayuda.

1 respuesta
jalamoNNN

#13 con el while sería algo así:
....
pides token del mutex;
while(iterator.hasNext()){ variableLocal = iterator.next(); liberas token; cálculos; pides token del mutex; }
liberas token;
....

Usuarios habituales

  • jalamoNNN
  • elraro
  • Lecherito
  • LLoid