concurrencia de Java: muchos escritores, un lector
Necesito recopilar algunas estadísticas en mi software y estoy tratando de hacerlo rápido y correcto, lo cual no es fácil (¡para mí!)
primero mi código hasta ahora con dos clases, un StatsService y un StatsHarvester
public class StatsService
{
private Map<String, Long> stats = new HashMap<String, Long>(1000);
public void notify ( String key )
{
Long value = 1l;
synchronized (stats)
{
if (stats.containsKey(key))
{
value = stats.get(key) + 1;
}
stats.put(key, value);
}
}
public Map<String, Long> getStats ( )
{
Map<String, Long> copy;
synchronized (stats)
{
copy = new HashMap<String, Long>(stats);
stats.clear();
}
return copy;
}
}
Esta es mi segunda clase, una cosechadora que recopila las estadísticas de vez en cuando y las escribe en una base de datos.
public class StatsHarvester implements Runnable
{
private StatsService statsService;
private Thread t;
public void init ( )
{
t = new Thread(this);
t.start();
}
public synchronized void run ( )
{
while (true)
{
try
{
wait(5 * 60 * 1000); // 5 minutes
collectAndSave();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
private void collectAndSave ( )
{
Map<String, Long> stats = statsService.getStats();
// do something like:
// saveRecords(stats);
}
}
En tiempo de ejecución tendrá aproximadamente 30 subprocesos simultáneos en ejecución cada llamadanotify(key)
Cerca de 100 veces. Solo un StatsHarvester está llamandostatsService.getStats()
Entonces tengo muchos escritores y un solo lector. Sería bueno tener estadísticas precisas, pero no me importa si se pierden algunos registros en alta concurrencia.
El lector debe ejecutar cada 5 minutos o lo que sea razonable.
La escritura debe ser lo más rápida posible. La lectura debe ser rápida, pero si se bloquea durante unos 300 ms cada 5 minutos, está bien.
He leído muchos documentos (concurrencia de Java en la práctica, Java efectivo, etc.), pero tengo la fuerte sensación de que necesito su consejo para hacerlo bien.
Espero haber declarado mi problema lo suficientemente claro y breve como para obtener ayuda valiosa.
EDITARGracias a todos por sus respuestas detalladas y útiles. Como esperaba, hay más de una forma de hacerlo.
Probé la mayoría de sus propuestas (las que entendí) y subí un proyecto de prueba al código de Google para obtener más referencias (proyecto maven)
http://code.google.com/p/javastats/
He probado diferentes implementaciones de mi StatsService
HashMapStatsService (HMSS)ConcurrentHashMapStatsService (CHMSS)LinkedQueueStatsService (LQSS)GoogleStatsService (GSS)ExecutorConcurrentHashMapStatsService (ECHMSS)ExecutorHashMapStatsService (EHMSS)y los probé conx
número de subprocesos que cada llamada notificay
veces, los resultados están en ms
10,100 10,1000 10,5000 50,100 50,1000 50,5000 100,100 100,1000 100,5000
GSS 1 5 17 7 21 117 7 37 254 Summe: 466
ECHMSS 1 6 21 5 32 132 8 54 249 Summe: 508
HMSS 1 8 45 8 52 233 11 103 449 Summe: 910
EHMSS 1 5 24 7 31 113 8 67 235 Summe: 491
CHMSS 1 2 9 3 11 40 7 26 72 Summe: 171
LQSS 0 3 11 3 16 56 6 27 144 Summe: 266
En este momento, creo que usaré ConcurrentHashMap, ya que ofrece un buen rendimiento y es bastante fácil de entender.
¡Gracias por todas sus aportaciones! Janning