thread safe example español java multithreading performance concurrency thread-safety

safe - ¿Debo siempre hacer que mi código java sea seguro para subprocesos o, por motivos de rendimiento, hacerlo solo cuando sea necesario?



java concurrency example (13)

Debe saber con certeza qué segmentos de su código serán de subprocesos múltiples y cuáles no.

Sin poder concentrar el área de multiproceso en una sección pequeña y controlable, no tendrá éxito. Las partes de su aplicación que tienen múltiples subprocesos deben revisarse cuidadosamente, analizarse, comprenderse y adaptarse para un entorno de subprocesos múltiples.

El resto no lo hace y, por lo tanto, hacerlo seguro a subprocesos sería un desperdicio.

Por ejemplo, con la GUI de swing, Sun simplemente decidió que nada de eso sería multihilo.

Ah, y si alguien usa tus clases, depende de ellos asegurarse de que, si está en una sección de subprocesos, hacerlo seguro.

Sun inicialmente salió con colecciones de threadsafe (solo). el problema es que el hilo no puede hacerse inseguro (para fines de rendimiento). Así que ahora salieron con versiones sin hilos con envoltorios para que sean seguros para los hilos. En la mayoría de los casos, las envolturas son innecesarias: supongamos que a menos que esté creando los hilos usted mismo, que su clase no tiene que ser segura para los hilos, pero DOCUMENTELA en los javadocs.

Si creo clases, que se usan en este momento solo en un solo hilo, ¿debo hacerlo seguro para subprocesos, incluso si no lo necesito en este momento? Podría suceder, que más tarde use esta clase en varios hilos, y en ese momento podría tener condiciones de carrera y podría tener dificultades para encontrarlos si no hubiera hecho la clase segura para subprocesos en primer lugar. ¿O debería hacer que la clase no sea segura para subprocesos, para un mejor rendimiento? Pero la optimización prematura es malvada.

Pregunta diferente: ¿Debo hacer que mis clases sean seguras para hilos si es necesario (si se usan en varios hilos, de lo contrario no) o debería optimizar este problema que necesito (si veo que la sincronización consume una parte importante del tiempo de procesamiento)?

Si elijo una de las dos formas, ¿existen métodos para reducir las desventajas? ¿O existe una tercera posibilidad, que debería usar?

EDITAR : Doy la razón por la cual se me ocurrió esta pregunta. En nuestra compañía, hemos escrito una administración de usuarios muy simple que escribe los datos en archivos de propiedades. Lo usé en una aplicación web y después de trabajar en él obtuve extraños errores, que la administración de usuarios olvidó las propiedades de los usuarios (incluido el nombre y la contraseña) y los roles. Eso fue muy molesto, pero no reproducible de manera consistente, así que creo que fue una condición de carrera. Como sincronicé todos los métodos de lectura y escritura desde / en el disco, el problema desapareció. Entonces pensé que probablemente me habrían evitado todas las molestias si hubiéramos escrito la clase con sincronización en primer lugar.

EDIT 2 : Cuando miro los consejos del programador pragmático, vi la sugerencia n. ° 41: Diseñar siempre para la concurrencia. Esto no dice que todo el código debe ser seguro para subprocesos, pero dice que el diseño debe tener la concurrencia en mente.


Diseñe por separado las clases para usar desde múltiples hilos y documente otras para usar desde un único hilo.

Los de rosca simple son mucho más fáciles de trabajar.

Separar la lógica multiproceso ayuda a hacer la sincronización correcta.


Siga el principio de "lo más simple posible, pero no más simple". En ausencia de un requisito, no debe hacerlos seguros para subprocesos. Hacerlo sería especulativo, y probablemente innecesario. La programación a prueba de hilos agrega mucha más complejidad a sus clases, y probablemente los hará menos efectivos debido a las tareas de sincronización.

A menos que se indique explícitamente que un objeto es seguro para subprocesos, la expectativa es que no lo es.


Comience desde los datos. Decida qué datos se comparten explícitamente y protéjalos. Si es posible, encapsule el bloqueo con los datos. Use colecciones simultáneas seguras para subprocesos existentes.

Siempre que sea posible, use objetos inmutables. Realice atributos finales, establezca sus valores en los constructores. Si necesita "cambiar" los datos, considere devolver una nueva instancia. Los objetos inmutables no necesitan bloqueo.

Para objetos que no están compartidos o limitados por hilos, no pierdas tiempo haciéndolos seguros para subprocesos.

Documenta las expectativas en el código. Las anotaciones JCIP son la mejor opción predefinida disponible.


Encontré las anotaciones JCIP muy útiles para declarar qué clases son seguras para subprocesos. Mi equipo anota nuestras clases como @ThreadSafe , @NotThreadSafe o @Immutable . Esto es mucho más claro que tener que leer Javadoc, y FindBugs nos ayuda a encontrar violaciones de los contratos @Immutable y @GuardedBy también.


Solo como una observación adicional: ¡Sincronización! = Seguridad de subprocesos. Aun así, es posible que no modifique los datos al mismo tiempo, pero puede leerlos al mismo tiempo. Por lo tanto, tenga en cuenta el Modelo de memoria de Java, donde la sincronización significa que los datos están disponibles de manera confiable en todos los hilos, no solo para proteger la modificación concurrente de los mismos.

Y sí, en mi opinión, la seguridad de subprocesos debe incorporarse desde el principio y depende de la lógica de la aplicación si necesita manejar la simultaneidad. Nunca asumas nada e incluso si tu prueba parece estar bien, las condiciones de carrera son perros dormidos.


Para evitar las condiciones de carrera, asegúrate solo de un objeto: lee las descripciones de las condiciones de la carrera y descubrirás que las cerraduras cruzadas (la condición de carrera es un nombre inapropiado) la carrera se detiene allí) son siempre una consecuencia de dos hilos + que intentan cerrarse dos + objetos.

Haga que todos los métodos estén sincronizados y realicen pruebas; para cualquier aplicación del mundo real que realmente tenga que lidiar con problemas, la sincronización es de bajo costo. Lo que no te dicen es que todo el bloqueo en las tablas de puntero de 16 bits ... en ese punto estás eh, ...

Solo mantén el actualizador de tu hamburguesa.


Aquí está mi enfoque personal:

  • Haz que los objetos y la estructura de datos sean inmutables siempre que puedas. Esa es una buena práctica en general, y es automáticamente segura para hilos. Problema resuelto.
  • Si tiene que hacer un objeto mutable, normalmente no se moleste en tratar de hacerlo seguro . El razonamiento para esto es simple: cuando tienes un estado mutable, el bloqueo / control no puede ser manejado con seguridad por una sola clase. Incluso si sincroniza todos los métodos, esto no garantiza la seguridad del hilo. Y si agrega sincronización a un objeto que solo se usa en un contexto de subproceso único, entonces acaba de agregar una sobrecarga innecesaria. Por lo tanto, es mejor dejar en manos de quien llama / usuario la implementación del sistema de bloqueo necesario.
  • Si proporciona una API pública de nivel superior, implemente cualquier bloqueo que sea necesario para que su thread de API sea seguro . Para una funcionalidad de nivel superior, la sobrecarga de la seguridad del hilo es bastante trivial, y sus usuarios definitivamente le agradecerán. ¡Una API con una semántica de concurrencia complicada que los usuarios deben solucionar no es una buena API!

Este enfoque me ha sido útil con el tiempo: es posible que tenga que hacer excepciones ocasionales, pero en promedio es un muy buen lugar para comenzar.


Si creo clases, que se usan en este momento solo en un solo hilo, ¿debo hacerlo seguro para subprocesos?

No es necesario que una clase utilizada por un subproceso por sí misma sea segura para subprocesos para que el programa en su totalidad sea seguro para subprocesos. Puede compartir con seguridad objetos de clases que no sean "hilos seguros" entre hilos si están protegidos por la sincronización adecuada. Por lo tanto, no es necesario crear una clase segura para subprocesos hasta que se vuelva aparente.

Sin embargo, multi-threading es la opción fundamental (arquitectónica) en un programa. No es realmente algo para agregar como un pensamiento posterior . Por lo tanto, debe saber desde el principio qué clases deben ser seguras para hilos.


"Siempre" es una palabra muy peligrosa en el desarrollo de software ... elecciones como esta son "siempre" situacionales.


Intenté hacer que todo fuera seguro para subprocesos; luego me di cuenta de que el significado de "hilo seguro" depende del uso. A menudo no se puede predecir ese uso, y la persona que llama tendrá que tomar medidas de todos modos para usarlo de manera segura.

En estos días escribo casi todo, asumiendo el enhebrado simple, y transfiero el conocimiento en los pocos lugares donde importa.

Habiendo dicho eso, también creo (cuando sea apropiado) tipos inmutables, que son naturalmente susceptibles de multi-threading, además de ser más fáciles de razonar en general.


Personalmente, solo diseñaría clases que son "thread-safe" cuando sea necesario, en el principio de optimizar solo cuando sea necesario. Sun parece haber seguido el mismo camino con el ejemplo de las clases de colecciones de subproceso único.

Sin embargo, hay algunos buenos principios que te ayudarán de cualquier manera si decides cambiar:

  1. Lo más importante: PIENSA ANTES DE SINCRONIZAR. Una vez tuve un colega que solía sincronizar cosas "por las dudas, después de todo, sincronizar debe ser mejor, ¿no?" Esto es INCORRECTO y fue la causa de varios errores de interbloqueo.
  2. Si tus objetos pueden ser inmutables, hazlos inmutables. Esto no solo ayudará con el enhebrado, sino que también ayudará a usarlo de forma segura en juegos, como claves para mapas, etc.
  3. Mantenga sus objetos lo más simple posible. Cada uno idealmente solo debe hacer un trabajo. Si alguna vez encuentra que podría querer sincronizar el acceso a la mitad de los miembros, entonces posiblemente deba dividir el Objeto en dos.
  4. Aprende java.util.concurrent y úsalo siempre que sea posible. Su código será mejor, más rápido y más seguro que el suyo (o el mío) en el 99% de los casos.
  5. Lea la Programación simultánea en Java , ¡es genial!

Si desea seguir lo que hizo Sun en la API de Java, puede echar un vistazo a las clases de colección. Muchas clases de colecciones comunes no son seguras para subprocesos, pero tienen contrapartidas seguras para subprocesos. Según Jon Skeet (ver comentarios), muchas de las clases de Java eran originalmente seguras para subprocesos, pero no beneficiaban a los desarrolladores, por lo que algunas clases ahora tienen dos versiones: una es segura para subprocesos y la otra no es segura para subprocesos.

Mi consejo es no hacer que el código sea seguro para subprocesos hasta que tenga que hacerlo, ya que hay una sobrecarga relacionada con la seguridad de subprocesos. Supongo que esto entra en la misma categoría que la optimización, no lo hagas antes de que tengas que hacerlo.