studio settitle programacion móviles libro desarrollo curso aplicaciones java multithreading concurrency thread-safety

java - settitle - ¿Cuánta seguridad del hilo es demasiado?



programacion android pdf 2018 (5)

He estado leyendo Java Concurrencia en la práctica últimamente - gran libro. Si crees que sabes cómo funciona la concurrencia, pero la mayoría de las veces que te enfrentas a los problemas reales, sientes que SWAG es lo máximo que puedes hacer, este libro sin duda arrojará algo de luz sobre el tema. Es un poco aterrador cómo muchas cosas pueden salir mal cuando intentas compartir datos entre hilos. Supongo que eso me volvió un poco loco por la seguridad de los hilos. Ahora mi preocupación es que, con un poco de sincronización, puedo encontrar algunos problemas de vida. Aquí hay un pedazo de código para ilustrar:

private final Hashtable<String, AtomicInteger> userSessions = new Hashtable<String, AtomicInteger>(); public void registerUser(String userLogin) { synchronized(userSessions) { AtomicInteger sessionCount = userSessions.get(userLogin); if (sessionCount != null) { sessionCount.incrementAndGet(); } else { userSessions.put(userLogin, new AtomicInteger(1)); } } } public void unregisterUser(String userLogin) { synchronized(userSessions) { AtomicInteger sessionCount = userSessions.get(userLogin); if (sessionCount != null) { sessionCount.decrementAndGet(); } } } public boolean isUserRegistered(String userLogin) { synchronized(userSessions) { AtomicInteger sessionCount = userSessions.get(userLogin); if (sessionCount == null) { return false; } return sessionCount.intValue() > 0; } }

Intenté hacerlo bien: la colección sincronizada se construyó en una sección estática y se almacenó en una referencia final estática para una publicación segura, bloqueó la colección (en lugar de this , para que no bloquee a toda la clase en la que vive el código) y use Clases de envolturas atómicas para primitivas. El libro menciona que exagerar esto también podría causar problemas, pero parece que necesito algo más de tiempo para rodearlo por completo. ¿Cómo haría que este código sea seguro para los subprocesos y asegurarse de que no sufra problemas de rendimiento y de vida?

EDITAR: lo convirtió en métodos y variables de instancia, originalmente todo fue declarado como estático - mal, mal diseño. También hice las sesiones de usuario privadas (de alguna manera lo dejé público antes).


Buen libro, recientemente lo leí yo mismo.

En el código anterior, la única nota que tengo es que AtomicInteger no es necesario dentro del bloque sincronizado, pero dudo que el rendimiento sea notable.

La mejor manera de realizar un seguimiento del rendimiento es probarlo. Configure una prueba de carga integrada automatizada alrededor de áreas clave de su marco y realice un seguimiento del rendimiento. La prueba de carga, si contiene ventanas de tiempo suficientemente amplias y un uso intensivo del flujo de trabajo, también puede detectar los puntos muertos que haya creado.

Si bien los puntos muertos pueden parecer fáciles de evitar, pueden aparecer fácilmente en un patrón de flujo de trabajo bastante simple.

La clase A bloquea los recursos y luego llama a B (puede ser tan simple como obtener / configurar) que también bloquea los recursos. Otro subproceso llama a B, que bloquea recursos y luego llama a A causando un interbloqueo.

Cuando se trabaja con un marco rico, es útil trazar un flujo de trabajo para ver cómo interactúan las clases. Es posible que pueda detectar este tipo de problema. Sin embargo, con marcos muy grandes, pueden deslizarse. La mejor defensa que he encontrado es aislar bloqueos en el área más pequeña posible y ser muy consciente de una llamada fuera de una clase mientras está dentro de un bloque sincronizado. Crear un número significativo de pruebas de carga también ayuda.


En primer lugar: ¡No uses el Hashtable! Es viejo, y muy lento.
Adicional: la sincronización en el nivel inferior no es necesaria si ya está sincronizado en un nivel superior (Esto también es válido para el objeto AtomicInteger).

Veo diferentes enfoques aquí, según el caso de uso que se necesite aquí.

El enfoque de lectura / escritura

Suponiendo que llame al método isUserRegistered muy a menudo y a los otros métodos solo de vez en cuando, una buena manera es un bloqueo de lectura-escritura: se permite tener múltiples lecturas al mismo tiempo, pero solo un bloqueo de escritura para gobernarlos a todos. (Solo se puede obtener si no se adquiere otro bloqueo).

private static final Map<String, Integer> _userSessions = new HashMap<String, Integer>(); private ReadWriteLock rwLock = new ReentrantReadWriteLock(false); //true for fair locks public static void registerUser(String userLogin) { Lock write = rwLock.writeLock(); write.lock(); try { Integer sessionCount = _userSessions.get(userLogin); if (sessionCount != null) { sessionCount = Integer.valueOf(sessionCount.inValue()+1); } else { sessionCount = Integer.valueOf(1) } _userSessions.put(userLogin, sessionCount); } finally { write.unlock(); } } public static void unregisterUser(String userLogin) { Lock write = rwLock.writeLock(); write.lock(); try { Integer sessionCount = _userSessions.get(userLogin); if (sessionCount != null) { sessionCount = Integer.valueOf(sessionCount.inValue()-1); } else { sessionCount = Integer.valueOf(0) } _userSessions.put(userLogin, sessionCount); } finally { write.unlock(); } } public static boolean isUserRegistered(String userLogin) { boolean result; Lock read = rwLock.readLock(); read.lock(); try { Integer sessionCount = _userSessions.get(userLogin); if (sessionCount != null) { result = sessionCount.intValue()>0 } else { result = false; } } finally { read.unlock(); } return false; }

Pro: simple de entender
Contras: no escalará si los métodos de escritura se llaman con frecuencia

El pequeño enfoque de la operación atómica.

La idea es hacer pequeños pasos, que son todos atómicos. Esto conducirá a un rendimiento muy bueno de todos modos, pero hay muchas trampas ocultas aquí.

public final ConcurrentMap<String, AtomicInteger> userSessions = new ConcurrentHashMap<String, AtomicInteger>(); //There are other concurrent Maps for different use cases public void registerUser(String userLogin) { AtomicInteger count; if (!userSession.containsKey(userLogin)){ AtomicInteger newCount = new AtomicInteger(0); count = userSessions.putIfAbsent(userLogin, newCount); if (count == null){ count=newCount; } //We need ifAbsent here, because another thread could have added it in the meantime } else { count = userSessions.get(userLogin); } count.incrementAndGet(); } public void unregisterUser(String userLogin) { AtomicInteger sessionCount = userSessions.get(userLogin); if (sessionCount != null) { sessionCount.decrementAndGet(); } } public boolean isUserRegistered(String userLogin) { AtomicInteger sessionCount = userSessions.get(userLogin); return sessionCount != null && sessionCount.intValue() > 0; }

Pro: escalas muy bien
Contras: no intuitivo, va a ser complejo rápidamente, no siempre es posible, muchas trampas ocultas

El bloqueo por enfoque de usuario.

Esto creará bloqueos para diferentes usuarios, asumiendo que hay muchos usuarios diferentes. Puede crear bloqueos o monitores con algunas operaciones atómicas pequeñas y bloquearlos en lugar de la lista completa.
Sería excesivo para este pequeño ejemplo, pero para estructuras muy complejas puede ser una solución elegante.


Me encontré con esta pregunta de hace años en busca de consejos sobre cómo hacer lo que uno podría llamar un "mapa de conteo concurrente", buscando en particular los usos de ConcurrentHashMap con AtomicInteger .

Aquí hay una versión modificada de la respuesta con la calificación más alta que usa AtomicInteger y no se AtomicInteger . En mi (limitado) prueba, esto parece mucho más rápido que la versión Integer . También observaré que usar ConcurrentMap.get() antes de ConcurrentMap.putIfAbsent() parece ahorrar un tiempo notable.

private final ConcurrentMap<String, AtomicInteger> userSessions = new ConcurrentHashMap<String, AtomicInteger>(); public void registerUser(String userLogin) { AtomicInteger oldCount = userSessions.get(key); if(oldCount!=null && getAndIncrementIfNonZero(oldCount)>0) return; AtomicInteger newCount = new AtomicInteger(1); while(true) { oldCount = userSessions.putIfAbsent(key, newCount); if(oldCount==null) return; if(getAndIncrementIfNonZero(oldCount)>0) return; } } public void unregisterUser(String userLogin) { AtomicInteger sessionCount = userSessions.get(userLogin); if (sessionCount != null) { int endingCount = sessionCount.decrementAndGet(); if(endingCount==0) userSessions.remove(userLogin); } } public boolean isUserRegistered(String userLogin) { AtomicInteger sessionCount = userSessions.get(userLogin); return sessionCount != null && sessionCount.intValue() > 0; } private static int getAndIncrementIfNonZero(AtomicInteger ai) { while(true) { int current = ai.get(); if(current==0) return 0; if(ai.compareAndSet(current, current+1)) return current; } }

La velocidad puede no ser tan relevante para el problema del póster original, pero otras aplicaciones de dicho "mapa de conteo" pueden beneficiarse de la eficiencia.


Use un ConcurrentHashMap para que pueda usar putIfAbsent . No necesita el código AtomicInteger para sincronizarse.

public final ConcurrentMap<String, AtomicInteger> userSessions = new ConcurrentHashMap<String, AtomicInteger>(); public void registerUser(String userLogin) { AtomicInteger newCount = new AtomicInteger(1); AtomicInteger oldCount = userSessions.putIfAbsent(userLogin, newCount); if (oldCount != null) { oldCount.incrementAndGet(); } } public void unregisterUser(String userLogin) { AtomicInteger sessionCount = userSessions.get(userLogin); if (sessionCount != null) { sessionCount.decrementAndGet(); } } public boolean isUserRegistered(String userLogin) { AtomicInteger sessionCount = userSessions.get(userLogin); return sessionCount != null && sessionCount.intValue() > 0; }

Tenga en cuenta, esto gotea ...

Intentar una versión sin fugas:

public final ConcurrentMap<String, Integer> userSessions = new ConcurrentHashMap<String, Integer>(); public void registerUser(String userLogin) { for (;;) { Integer old = userSessions.get(userLogin); if (userSessions.replace(userLogin, old, old==null ? 1 : (old+1)) { break; } } } public void unregisterUser(String userLogin) { for (;;) { Integer old = userSessions.get(userLogin); if (old == null) { // Wasn''t registered - nothing to do. break; } else if (old == 1) { // Last one - attempt removal. if (userSessions.remove(userLogin, old)) { break; } } else { // Many - attempt decrement. if (userSessions.replace(userLogin, old, old-1) { break; } } } } public boolean isUserRegistered(String userLogin) {serLogin); return userSessions.containsKey(userLogin); }


Vista desde su código, su sincronización en _userSessions debería ser suficiente porque no expone los objetos AtomicInteger .

La seguridad adicional ofrecida por AtomicInteger no es necesaria en este caso, por lo que, en esencia, se usa aquí como un Integer mutable. Podría colocar una clase estática anidada que contenga el recuento de sesiones como único atributo en el mapa si le preocupa la sobrecarga adicional en AtomicInteger (o un poco más feo: agregue int [1] ''s al mapa siempre que no expuesto fuera de esta clase.