2008-10-24 12 views
22

Si creo clases, que se usan en este momento solo en un solo hilo, ¿debo hacerlas seguras para subprocesos, incluso si no las 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.¿Debo siempre hacer que mi código java sea seguro para subprocesos o, por motivos de rendimiento, hacerlo solo cuando sea necesario?

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?)?

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

EDIT: explico el motivo por el 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 las puntas 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.

+0

Algo para ayudar a su análisis es no pensar en la concurrencia como una optimización prematura, sino más bien en una preocupación de rendimiento similar a BigO. Las observaciones de optimization-is-evil prematuras se refieren a personas que desenrollan bucles o construcciones condicionales crípticas switch-if en C/C++ que son imposibles de descifrar y mantener, y pueden no proporcionarle ninguna ganancia de rendimiento una vez que el compilador ha terminado código (o en java el tiempo de ejecución JIT). – reccles

Respuesta

19

Empezar a partir de 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 los objetos que no se comparten ni se limitan a hilos, no pierda tiempo haciéndolos seguros para hilos.

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

+0

Me gusta esta respuesta. Usted decide de una manera (no sincronizar demasiado pronto), pero también proporciona algunas soluciones que ayudarán a reducir el peligro de tener problemas. – Mnementh

+1

+1 por mencionar las anotaciones JCIP. –

0

Si quiere seguir lo que hizo Sun en la API de Java, puede echar un vistazo a las clases de la 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 cae en la misma categoría que la optimización, no lo hagas antes de que tengas que hacerlo.

+0

En realidad, el enfoque de Sun era al revés: las clases de colección originales (y StringBuffer) eran seguras para subprocesos, luego se dieron cuenta de que realmente no ayudaban a nadie, por lo que cuando rediseñaron las clases de colección las hicieron no thread-safe . –

+0

¿De verdad? Corregiré mi publicación para asegurarme de que la historia sea correcta. Pero sé que Sun lo hizo por motivos de rendimiento, que fue mi punto. Gracias. –

0

Diseñe por separado las clases a usar desde múltiples hilos y documente las que se utilizarán a partir de un solo hilo.

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

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

26

Solía ​​intentar que todo fuera seguro para subprocesos, entonces 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 va a tener para tomar medidas de todos modos para usarlo de una manera segura para hilos.

En estos días escribo casi todo asumiendo el enhebrado simple, y pongo el conocimiento del hilo en los pocos lugares seleccionados en los que 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.

+0

¿Tiene un ejemplo, donde el uso puede romper la seguridad de la hebra de una clase? – Mnementh

+7

Muy fácilmente: tome una colección donde cada operación individual sea segura para hilos en sí misma. Ahora repítelo - bang, thread-safety se ha ido. No desea que cada operación individual elimine un bloqueo; necesita un bloqueo durante todo el tiempo que está iterando. La recopilación en sí misma no puede hacer eso por ti. –

+0

OK, con esta explicación, su publicación vale la pena un voto popular. Gracias. – Mnementh

6

Siga el prinicple 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.

1

Debe saber 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 para hilos sería un desperdicio.

Por ejemplo, con la GUI de oscilación, Sun simplemente decidió que nada de eso sería de subprocesos múltiples.

Ah, y si alguien utiliza sus clases, es su responsabilidad asegurarse de que si está en una sección con hilos, hágalo seguro.

Sun inicialmente salió con colecciones de threadsafe (solamente). 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, los wrappers son innecesarios: supongamos que a menos que esté creando los hilos usted mismo, que su clase no tenga que ser segura para los hilos, pero DOCUMENTELO en los javadocs.

0

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

4

Personalmente, solo diseñaría clases que sean "seguras para hilos" 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 le ayudarán en cualquier caso si decide cambiar:

  1. Lo más importante: pensar antes de realizar la sincronización. 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 sus objetos pueden ser inmutables, hágalos inmutables. Esto no solo ayudará con el enhebrado, sino que también ayudará a usarlo con seguridad 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 Concurrent Programming in Java, es genial!
+0

@Nick: me interesó ver su comentario de que la sincronización causó múltiples errores de interbloqueo, ¿consideraría hacer una pregunta de estilo de riesgo en la que pregunta cómo puede una pieza sincronizada de código causar interbloqueos y proporcionar una respuesta? Sería muy informativo escuchar esto. –

+0

@Nick: dado que un hilo no puede estancarse consigo mismo, la aplicación que se estanca debe tener múltiples hilos que accedan a los mismos objetos. Por lo tanto, definitivamente se requería alguna sincronización. –

+0

Tienes razón, no estoy diciendo que la sincronización sea mala, obviamente no lo es. Estoy diciendo que la sincronización sin pensar es mala. –

2

Encontré que las anotaciones JCIP son 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.

3

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, y no solo protege la modificación concurrente de los mismos.

Y sí, en mi opinión, la seguridad de subprocesos tiene que 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.

0

Para evitar las condiciones de carrera, traba con un solo objeto: lea las descripciones de las condiciones de carrera y descubrirá que las cerraduras cruzadas (condición de carrera es un nombre inapropiado) son siempre una consecuencia de dos hilos tratando de bloquear dos objetos +.

Haga que todos los métodos se sincronicen y realicen pruebas; para cualquier aplicación del mundo real que realmente tenga que lidiar con los problemas, la sincronización tiene un costo bajo. Lo que no le dicen es que todo se bloquea en las tablas de puntero de 16 bits ... en ese punto usted está eh, ...

Simplemente mantenga la flippin de su hamburguesa reanudada.

0

Si creo clases, que se utilizan en el momento solamente en un solo hilo, en caso de que les flujos seguros

hacer que no es necesario para una clase utilizada por un hilo que por sí sola seguro para subprocesos para que el programa como un todo sea seguro para subprocesos. Puede compartir con seguridad objetos de clases que no sean "hilos seguros" entre los 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 elección fundamental (arquitectónica) en un programa. Es not really something to add as an after thought. Por lo tanto, debe saber desde el principio qué clases deben ser seguras para hilos.

1

Aquí es mi enfoque personal:

  • hacer que los objetos y estructuras de datos inmutables donde se pueda. Esa es una buena práctica en general, y es automáticamente segura para hilos. Problema resuelto.
  • Si tiene que hacer que un objeto sea mutable, normalmente no se moleste en intentar que sea seguro para hilos.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, entonces implementará el bloqueo necesario para que su thread 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 servido bien en el tiempo: es posible que tenga que hacer la excepción ocasional pero, en promedio, ¡es un muy buen lugar para comenzar!

Cuestiones relacionadas