Tengo una aplicación web en la que estoy realizando pruebas de carga/rendimiento, especialmente en una función en la que esperamos que unos pocos cientos de usuarios accedan a la misma página y actualicen todos los elementos 10 segundos en esta página. Un área de mejora que encontramos que podíamos hacer con esta función era almacenar en caché las respuestas del servicio web durante un período de tiempo, ya que los datos no están cambiando.Sincronización en objetos String en Java
Después de implementar este almacenamiento en caché básico, en algunas pruebas adicionales descubrí que no tenía en cuenta cómo los subprocesos simultáneos podían acceder al caché al mismo tiempo. Descubrí que en cuestión de ~ 100 ms, aproximadamente 50 hilos intentaban recuperar el objeto de la memoria caché, encontrando que había expirado, presionando el servicio web para recuperar los datos, y luego colocando el objeto nuevamente en la memoria caché.
El código original parecía algo como esto:
private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
final String key = "Data-" + email;
SomeData[] data = (SomeData[]) StaticCache.get(key);
if (data == null) {
data = service.getSomeDataForEmail(email);
StaticCache.set(key, data, CACHE_TIME);
}
else {
logger.debug("getSomeDataForEmail: using cached object");
}
return data;
}
Por lo tanto, para asegurarse de que sólo un hilo estaba llamando al servicio web cuando el objeto en key
expiró, pensé que necesitaba para sincronizar la caché obtener/estableció la operación, y parecía que usar la clave de caché sería un buen candidato para que un objeto se sincronice (de esta forma, las llamadas a este método para el correo electrónico [email protected] no serían bloqueadas por llamadas de método a [email protected])
he actualizado el método para tener este aspecto:
private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
SomeData[] data = null;
final String key = "Data-" + email;
synchronized(key) {
data =(SomeData[]) StaticCache.get(key);
if (data == null) {
data = service.getSomeDataForEmail(email);
StaticCache.set(key, data, CACHE_TIME);
}
else {
logger.debug("getSomeDataForEmail: using cached object");
}
}
return data;
}
También he añadido el registro líneas para cosas como "antes del bloque de sincronización", "dentro de bloque de sincronización", "punto de salir de bloque de sincronización", y " después del bloque de sincronización ", así pude determinar si estaba sincronizando efectivamente la operación get/set.
Sin embargo, no parece que esto haya funcionado. Mis registros de prueba tienen una salida como:
(registro de salida es 'threadName' 'Nombre del registrador' 'mensaje')
http-80-Processor253 jsp.view páginas - getSomeDataForEmail: a punto de entrar en bloque de sincronización
http-80-Processor253-página jsp.view - getSomeDataForEmail: dentro de bloque de sincronización
http-80-Processor253 cache.StaticCache - obtener: objeto en tecla [[email protected]] ha expirado
http-80-Processor253 cache.StaticCache - get: clave [[email protected]] devuelve el valor [nulo]
http-80-Processor263 jsp.view-page - getSomeDataForEmai l: a punto de ingresar al bloque de sincronización
http-80-Processor263 jsp.view-page - getSomeDataForEmail: dentro del bloque de sincronización
http-80-Processor263 cache.StaticCache - get: object at key [[email protected]] ha expirado
http-80-Processor263 cache.StaticCache - get: clave [[email protected]] devuelve el valor [nulo]
http-80-Processor131 jsp.view-page - getSomeDataForEmail: está a punto de ingresar al bloque de sincronización
-página jsp.view http-80-Processor131 - getSomeDataForEmail: dentro de bloque de sincronización
http-80-Processor131 cache.StaticCache - obtener: objeto en tecla [[email protected]] ha expirado
http-80-Processor131 cache.StaticCache - obtener: tecla [[email protected]] valor de volver [nulo]
http-80-Processor104 jsp.view-página - getSomeDataForEmail: dentro de bloque de sincronización
http-80 -Processor104 caché.StaticCache - get: objeto en la clave [[email protected]] ha caducado
http-80-Processor104 cache.StaticCache - get: clave [[email protected]] devuelve el valor [nulo]
http- 80-Processor252 jsp.view-página - getSomeDataForEmail: punto de entrar en bloque de sincronización
http-80-Processor283 jsp.view-página - getSomeDataForEmail: punto de entrar en bloque de sincronización
http-80-Processor2 jsp.view-página - getSomeDataForEmail : punto de entrar en bloque de sincronización
http-80-Processor2-página jsp.view - getSomeDataForEmail: dentro de bloque de sincronización
Quería ver solo un hilo a la vez que ingresaba/salía del bloque de sincronización alrededor de las operaciones get/set.
¿Hay un problema en la sincronización de objetos String? Pensé que la clave de caché sería una buena opción, ya que es única para la operación, y aunque el final String key
se haya anunciado en el método, yo estaba pensando que cada hilo estaría consiguiendo una referencia a el mismo objeto y por lo tanto sería sincronización en este único objeto.
¿Qué estoy haciendo mal aquí?
actualización: después de mirar más a fondo en los registros, parece que los métodos con la misma lógica de sincronización, donde la clave es siempre la misma, tales como
final String key = "blah";
...
synchronized(key) { ...
no presentan el mismo problema de concurrencia - solamente un hilo a la vez está ingresando al bloque.
Actualización 2: ¡Gracias a todos por la ayuda! Me aceptado la primera respuesta sobre intern()
ing Cuerdas, que resolvió mi problema inicial - donde varios hilos estaban entrando en bloques sincronizados en el que pensé que no deberían, ya que los key
's tenían el mismo valor.
Como han señalado otros, utilizar intern()
para tal propósito y sincronizar en esas cadenas realmente resulta ser una mala idea: al ejecutar pruebas JMeter contra la aplicación web para simular la carga esperada, vi el tamaño de pila utilizado crecen a casi 1GB en poco menos de 20 minutos.
Actualmente estoy usando la solución simple de tan sólo sincronizar el método completo - pero realmente como los ejemplos de código proporcionados por martinprobst y MBCook, pero ya que tengo unos 7 getData()
métodos similares de esta clase actualmente (ya que necesita aproximadamente 7 datos diferentes de un servicio web), no quería agregar lógica casi duplicada para obtener y liberar bloqueos para cada método. Pero esta es definitivamente información muy, muy valiosa para uso futuro. Creo que en última instancia, estas son las respuestas correctas sobre la mejor manera de hacer una operación como este thread-safe, ¡y daría más votos a estas respuestas si pudiera!
Usted ya no tendrá que w orry sobre el interno Cadena que cuelga en la memoria: las cadenas aparentemente internados han sido sometidas a GC por bastante tiempo: http://stackoverflow.com/questions/18152560/garbage-collection-on-internd-strings-string- pool-and-perm-space – Volksman
Recomiendo esta respuesta, usando Guava's Striped para evitar el uso excesivo de memoria: https://stackoverflow.com/a/11125602/116810 –