significado rosca reyes porque parte origen mexico informativa historia ficha cuál cuando come buscar java multithreading thread-safety

java - porque - rosca de reyes historia y significado



¿Algún enfoque satisfactorio para probar la seguridad de la rosca de la unidad en Java? (5)

Estoy buscando mejorar un paquete que no creo que sea seguro para la ejecución de subprocesos cuando su entrada se comparte entre múltiples subprocesos de trabajo. De acuerdo con los principios de TDD, debo escribir algunas pruebas que fallan en primera instancia, y ciertamente serían útiles para evaluar el problema.

Me doy cuenta de que esto no es algo fácil de lograr, y que ingenuamente, las pruebas de múltiples subprocesos no serán deterministas ya que el sistema operativo determinará la programación y el orden exacto en que se intercalarán varias operaciones. He analizado y utilizado MultithreadedTC en el pasado, y esto fue útil. Sin embargo, en ese caso supe de antemano exactamente dónde se cayó la implementación existente, y así pude cocinar un buen conjunto de pruebas que lo cubrieron.

Sin embargo, si no está en el punto donde sabe exactamente cuál es el problema, ¿hay una buena forma de escribir una prueba que represente una buena posibilidad de plantear algún problema potencial? ¿Hay bibliotecas que otros hayan encontrado útiles? ¿Tendría razón al pensar que, desde un punto de vista purista, un caso de prueba multiproceso debería ser las mismas llamadas y aserciones que la prueba de un solo subproceso habitual, solo ejecutarse con múltiples subprocesos de trabajo, según corresponda?

Cualquier oferta sobre herramientas / mejores prácticas / filosofía en general sería bienvenida.


En algunos casos, he descubierto que puedo forzar un particular intercalamiento problemático de llamadas a una clase potencialmente no segura para subprocesos mediante el uso de varios subprocesos que se sincronizan entre sí, tal vez con el uso de CountDownLatch o algún mecanismo de simultaneidad. Sin embargo, a veces esto simplemente no funciona, por ejemplo, si intentas probar qué sucede si dos hilos están en el mismo método al mismo tiempo.

Aquí hay un artículo interesante (aunque no sé mucho sobre la herramienta): http://today.java.net/pub/a/today/2003/08/06/multithreadedTests.html


Olvídate de obtener buenos resultados probando problemas de concurrencia. Intente reducir la sincronización y reduzca el problema. A continuación, use la mayor compatibilidad de biblioteca posible para realizar la sincronización. Y solo si realmente tiene, entonces intente manejar la concurrencia usted mismo. Cuando sabes que cada trabajador hace su trabajo correctamente y todo tu pensamiento te dice que tienes el problema de simultaneidad, entonces genera una carga interesante. Los frameworks de Unittest y sus extensiones pueden hacer ese trabajo, pero debes saber que ya no estás probando ninguna unidad. (Recuerde, ya tenía esa parte cubierta)

Si su modelo de simultaneidad se vuelve complicado, revise las herramientas adecuadas para eso, como SPIN .


Java Concurrency in Practice tiene buena información sobre cómo escribir pruebas para problemas de concurrencia. Sin embargo, no son verdaderas pruebas unitarias. Es casi imposible escribir una prueba unitaria verdadera para un problema de concurrencia.

Básicamente se reduce a esto. Cree un conjunto de hilos de prueba e inícielos. Cada hilo debe

  • esperar a que baje la cuenta atrás
  • Llamar repetidamente a algún método que modifique el estado mutable en cuestión
  • cuenta atrás en un segundo pestillo y sale

El subproceso junit crea todos los subprocesos y los inicia, luego realiza una cuenta atrás en el primer pestillo una vez para dejarlos ir, luego espera el segundo pestillo y luego hace algunas aserciones sobre el estado mutable.

Incluso más que otros tipos de errores, es más fácil escribir una prueba de falla de unidad para un error de concurrencia después de haber encontrado el error.


Hay dos problemas básicos que debes resolver. El primero es este: ¿cómo se genera un choque de hilo? En segundo lugar, ¿cómo valida su prueba?

El primero es sencillo. Usa un martillo grande Escriba un código que sea capaz de detectar el caso en que un hilo pasa por otro, y ejecute ese código 50 veces más o menos en 50 hilos consecutivos. Puede hacer esto con un bloqueo de cuenta atrás:

public void testForThreadClash() { final CountDownLatch latch = new CountDownLatch(1); for (int i=0; i<50; ++i) { Runnable runner = new Runnable() { public void run() { try { latch.await(); testMethod(); } catch (InterruptedException ie) { } } } new Thread(runner, "TestThread"+i).start(); } // all threads are waiting on the latch. latch.countDown(); // release the latch // all threads are now running concurrently. }

Su testMethod () tiene que generar una situación en la que, sin el bloque sincronizado, un hilo pase por los datos de otro hilo. Debería ser capaz de detectar este caso y lanzar una excepción. (El código anterior no hace esto. Es muy probable que la excepción se genere en uno de los hilos de prueba, y debe poner un mecanismo que lo detecte en el hilo principal, pero eso es un problema aparte. Lo dejé por simplicidad, pero puede hacer esto con un segundo pestillo, que la prueba puede borrar prematuramente en una excepción).

La segunda pregunta es más complicada, pero hay una solución simple. La pregunta es esta: ¿Cómo sabes que tu martillo generará un choque? Por supuesto, su código tiene bloques sincronizados para evitar un choque, por lo que no puede responder la pregunta. Pero puedes. Solo elimine las palabras clave sincronizadas. Dado que es la palabra clave sincronizada la que hace que su clase sea segura en cuanto a los hilos, puede simplemente eliminarlos y volver a ejecutar la prueba. Si la prueba es válida, ahora fallará.

Cuando escribí el código anterior, resultó ser inválido. Nunca vi un enfrentamiento. Entonces no es (todavía) una prueba válida. Pero ahora sabemos cómo obtener una respuesta clara a la segunda pregunta. Estamos obteniendo la respuesta incorrecta, pero ahora podemos jugar con la prueba para generar el error que estamos buscando. Esto es lo que hice: realicé la prueba 100 veces seguidas.

for (int j=0; j<100; ++j) { testForThreadClash(); }

Ahora mi prueba falló confiablemente en la 20ma iteración más o menos. Esto confirma que mi prueba es válida. Ahora puedo restaurar la palabra clave sincronizada y volver a ejecutar la prueba, confiando en que me dirá si mi clase es segura para los hilos.


No hay nada de malo con la unidad de prueba de código de subprocesos múltiples si el enhebrado es el punto del código que está probando (piense en estructuras de datos concurrentes). El enfoque general es bloquear el hilo de prueba principal, realizar aserciones en hilos separados, capturar y volver a lanzar cualquier aserción fallida en el hilo principal, de lo contrario, desbloquear el hilo de prueba principal para que la prueba se complete. Es bastante sencillo de hacer con ConcurrentUnit :

@Test public void shouldDeliverMessage() throws Throwable { final Waiter waiter = new Waiter(); messageBus.registerHandler(message -> { // Called on separate thread waiter.assertEquals(message, "foo"); // Unblocks the waiter.await call waiter.resume(); }; messageBus.send("foo"); // Wait for resume() to be called waiter.await(1000); }

La clave aquí es que cualquier afirmación fallida en cualquier hilo será relanzada en el hilo principal por el waiter , permitiendo que la prueba pase o falle como debería.