En los ejemplos anteriores de subprocesamiento en Java, utilizabamos la sincronización para garantizar que dos hilos manipulen correctamente los datos en un búfer compartido. Sin embargo, es probable que nuestra aplicación no tenga un rendimiento óptimo. Si los dos subprocesos operan a distintas velocidades, es probable que uno de ellos pase la mayor parte del tiempo esperando. Por ejemplo, en los anteriores ejercicios compartiamos una variable entera. Entonces, si por ejemplo el productor realiza sus tareas a una mayor velocidad que el consumidor, este debe esperar al consumidor; y viseversa. Incluso, suponiendo que los subprocesos se ejecutan a la misma velocidad relativa, cabe la posibilidad que uno u otro proceso se retrase, haciendo que se pierda la sincronización entre los mismos.
Como programadores, no debemos hacer suposiciones acerca de las velocidades relativas de los subprocesos concurrentes asíncronos, puesto que existen muchos factores que pueden afectar el tiempo de ejecución de los subprocesos (el sistema operativo, el usuario, el entorno de red, etc.).
El método que vamos a emplear en este ejemplo para minimizar el tiempo de espera que sufren los subprocesos que comparten recursos es crear un búfer circular. Con este nuevo enfoque, en vez de tener una única variable compartida, tendremos un arreglo de búferes. De esta forma, si el productor produce valores a mayor velocidad de la que el consumidor pueda consumirlos, almacenará los valores en posiciones distintas del arreglo (mientras haya espacio). Esto permite al productor hacer su tarea, aún cuando el consumidor no esté listo para realizar la suya. De igual forma, si en ciertas ocaciones el consumidor recoge valores a mayor velocidad de la que el productor pueda consumirlos, este puede buscar otros valores adicionales en el arreglo (si los hay) que el productor pudo haber producido con anterioridad.
Observa además, querido lector, que usar un búfer circular sería inapropiado si los procesos se ejecutan a diferentes velocidades conscientemente. Es decir, si por ejemplo sabemos que el consumidor se ejecuta siempre a mayor velocidad, no necesitariamos espacios adicionales puesto que el productor no tendría tiempo de llenarlas. De igual forma, si el productor se ejecutase siempre a mayor velocidad que el consumidor, necesitariamos un arreglo con infinitas posiciones.
En el ejemplo que veremos en breve (muy similar a los que habíamos visto antes), demuestra cómo un productor y un consumidor utilizan un búfer circular (en este caso un arreglo de tres celdas) mediante la sincronización.
Además, como valor agregado, veremos algo avanzado acerca de las interfaces gráficas. En este ejemplo tanto productor como consumidor (procesos independientes), modifican el contenido de un objeto de interfaz gráfica (en este caso un JTextArea). Puesto que los componentes gráficos de Swing no son "a prueba de subprocesos", cabe la posibilidad de que si diferentes tareas están manipulando el mismo componente los resultados visuales no sean correctos. Todas las interacciones sobre los objetos GUI de Swing deben llevarse a cabo un proceso a la vez. Generalmente, este subproceso es el subproceso despachador de eventos (tambien conocido como el subproceso manejador de eventos). La clase SwingUtilities (del paquete java.swing), proporciona el método estático invokeLater para ayudar a realizar esta tarea. El método invokeLater especifica que las instrucciones de procesamiento de la GUI deben ejecutarse posteriormente, como parte del subproceso despachador de eventos. El método invokeLater recibe un objeto que implemente la clase Runnable (del paquete java.lang). Toda clase que implemente Runnable debe declarar el método run(). Los subprocesos en el siguiente ejemplo, que muestran su salida en la GUI, pasan objetos de la clase SalidaRunnable al método invokeLater. Cada objeto SalidaRunnable contiene una referencia al objeto JTextArea en el que se muestra la salida, y una cadena del mensaje a mostrar. Cuando el programa llama a invokeLater, la actualización del componente GUI se pondrá en la cola para ejecutarse en el subproceso despachador de eventos. Luego, el método run de la clase SalidaRunnable será invocado, haciendo el el componente JTextArea se ejecute actulice de forma segura.
Resultado del ejemplo


Código fuente
La interfaz Bufer.java especifica los métodos llamados por el Productor y el Consumidor: Leer el resto de la entrada...