Introducción a los hilos de Java

Este artículo, uno de los primeros publicados por JavaWorld, describe cómo se implementan los subprocesos en el lenguaje de programación Java, comenzando con una descripción general de los subprocesos.

En pocas palabras, un hilo es la ruta de ejecución de un programa. La mayoría de los programas escritos hoy en día se ejecutan como un solo hilo, lo que causa problemas cuando es necesario que ocurran varios eventos o acciones al mismo tiempo. Digamos, por ejemplo, que un programa no es capaz de hacer dibujos mientras lee las pulsaciones de teclas. El programa debe prestar toda su atención a la entrada del teclado que carece de la capacidad de manejar más de un evento a la vez. La solución ideal a este problema es la ejecución perfecta de dos o más secciones de un programa al mismo tiempo. Threads nos permite hacer esto.

Aprendiendo sobre los hilos de Java

Este artículo es parte del archivo de contenido técnico de JavaWorld. Consulte lo siguiente para obtener más información sobre la simultaneidad y los subprocesos de Java:

Comprensión de los subprocesos de Java ( serie Java 101 , 2002):

  • Parte 1: Introducción a subprocesos y ejecutables
  • Parte 2: sincronización de subprocesos
  • Parte 3: programación de subprocesos y espera / notificación
  • Parte 4: Grupos de hilos y volatilidad

Artículos relacionados

  • Java con hiperproceso: uso de la API de concurrencia de Java (2006)
  • Mejores monitores para programas multiproceso (2007)
  • Comprensión de la concurrencia de actores, parte 1 (2009)
  • Detección y manipulación de hilos colgantes (2011)

Consulte también el mapa del sitio JavaWorld y el motor de búsqueda .

Las aplicaciones multiproceso ofrecen su gran potencia al ejecutar muchos subprocesos al mismo tiempo dentro de un solo programa. Desde un punto de vista lógico, multihilo significa que se pueden ejecutar varias líneas de un solo programa al mismo tiempo, sin embargo, no es lo mismo que iniciar un programa dos veces y decir que hay varias líneas de un programa ejecutándose al mismo tiempo. hora. En este caso, el sistema operativo trata los programas como dos procesos separados y distintos. En Unix, la bifurcación de un proceso crea un proceso hijo con un espacio de direcciones diferente tanto para el código como para los datos. Sin embargo,fork()crea una gran sobrecarga para el sistema operativo, lo que la convierte en una operación que consume mucha CPU. Al iniciar un hilo en su lugar, se crea una ruta de ejecución eficiente mientras se comparte el área de datos original del padre. La idea de compartir el área de datos es muy beneficiosa, pero plantea algunas áreas de preocupación que discutiremos más adelante.

Creando hilos

Los creadores de Java han diseñado gentilmente dos formas de crear hilos: implementar una interfaz y extender una clase. La extensión de una clase es la forma en que Java hereda métodos y variables de una clase principal. En este caso, solo se puede extender o heredar de una sola clase padre. Esta limitación dentro de Java se puede superar implementando interfaces, que es la forma más común de crear subprocesos. (Tenga en cuenta que el acto de heredar simplemente permite que la clase se ejecute como un subproceso. Depende de la clase la start()ejecución, etc.)

Las interfaces proporcionan a los programadores una forma de sentar las bases de una clase. Se utilizan para diseñar los requisitos para implementar un conjunto de clases. La interfaz configura todo y la clase o clases que implementan la interfaz hacen todo el trabajo. Los diferentes conjuntos de clases que implementan la interfaz deben seguir las mismas reglas.

Hay algunas diferencias entre una clase y una interfaz. Primero, una interfaz solo puede contener métodos abstractos y / o variables finales estáticas (constantes). Las clases, por otro lado, pueden implementar métodos y contener variables que no son constantes. En segundo lugar, una interfaz no puede implementar ningún método. Una clase que implementa una interfaz debe implementar todos los métodos definidos en esa interfaz. Una interfaz tiene la capacidad de extenderse desde otras interfaces y (a diferencia de las clases) puede extenderse desde múltiples interfaces. Además, no se puede crear una instancia de una interfaz con el nuevo operador; por ejemplo, Runnable a=new Runnable();no está permitido.

El primer método para crear un hilo es simplemente extenderse desde la Threadclase. Haga esto solo si la clase que necesita ejecutar como un hilo no necesita ser extendida desde otra clase. La Threadclase está definida en el paquete java.lang, que debe importarse para que nuestras clases conozcan su definición.

import java.lang.*; public class Counter extends Thread { public void run() { .... } }

El ejemplo anterior crea una nueva clase Counterque amplía la Threadclase y anula el Thread.run()método para su propia implementación. El run()método es donde se realiza todo el trabajo del Counterhilo de la clase. La misma clase se puede crear implementando Runnable:

import java.lang.*; public class Counter implements Runnable { Thread T; public void run() { .... } }

Aquí, el run()método abstracto se define en la interfaz Runnable y se está implementando. Tenga en cuenta que tenemos una instancia de la Threadclase como variable de la Counterclase. La única diferencia entre los dos métodos es que al implementar Runnable, existe una mayor flexibilidad en la creación de la clase Counter. En el ejemplo anterior, todavía existe la oportunidad de ampliar la Counterclase, si es necesario. La mayoría de las clases creadas que necesitan ejecutarse como un hilo implementarán Runnable ya que probablemente están extendiendo alguna otra funcionalidad de otra clase.

No piense que la interfaz Runnable está haciendo un trabajo real cuando se está ejecutando el hilo. Es simplemente una clase creada para dar una idea sobre el diseño de la Threadclase. De hecho, es muy pequeño y contiene solo un método abstracto. Aquí está la definición de la interfaz Runnable directamente desde la fuente de Java:

package java.lang; public interface Runnable { public abstract void run(); }

Eso es todo lo que hay en la interfaz Runnable. Una interfaz solo proporciona un diseño sobre el que se deben implementar las clases. En el caso de la interfaz Runnable, fuerza la definición de solo el run()método. Por lo tanto, la mayor parte del trabajo se realiza en Threadclase. Una mirada más cercana a una sección en la definición de la Threadclase dará una idea de lo que realmente está sucediendo:

public class Thread implements Runnable { ... public void run() { if (target != null) { target.run(); } } ... }

A partir del fragmento de código anterior, es evidente que la clase Thread también implementa la interfaz Runnable. Thread. run()comprueba para asegurarse de que la clase de destino (la clase que se ejecutará como un hilo) no es igual a nulo, y luego ejecuta el run()método del destino. Cuando esto sucede, el run()método del destino se ejecutará como su propio hilo.

Arranque y parada

Dado que las diferentes formas de crear una instancia de un subproceso ahora son evidentes, discutiremos la implementación de subprocesos comenzando con las formas disponibles para iniciarlos y detenerlos usando un pequeño applet que contiene un subproceso para ilustrar la mecánica:

Ejemplo de CounterThread y código fuente

El subprograma anterior comenzará a contar desde 0 mostrando su salida tanto en la pantalla como en la consola. Un vistazo rápido puede dar la impresión de que el programa comenzará a contar y mostrará todos los números, pero este no es el caso. Un examen más detenido de la ejecución de este applet revelará su verdadera identidad.

In this case, the CounterThread class was forced to implement Runnable since it extended the class Applet. As in all applets, the init() method gets executed first. In init(), the variable Count is initialized to zero and a new instance of the Thread class is created. By passing this to the Thread constructor, the new thread will know which object to run. In this case this is a reference to CounterThread. After the thread is created it needs to be started. The call to start() will call the target's run() method, which is CounterThread.run(). The call to start() will return right away and the thread will start executing at the same time. Note that the run() method is an infinite loop. It is infinite because once the run() method exits, the thread stops executing. The run() method will increment the variable Count, sleep for 10 milliseconds and send a request to refresh the applet's display.

Note that it is important to sleep somewhere in a thread. If not, the thread will consume all CPU time for the process and will not allow any other methods such as threads to be executed. Another way to cease the execution of a thread is to call the stop() method. In this example, the thread stops when the mouse is pressed while the cursor is in the applet. Depending on the speed of the computer the applet runs on, not every number will be displayed, because the incrementing is done independent of the painting of the applet. The applet can not be refreshed at every request, so the OS will queue the requests and successive refresh requests will be satisfied with one refresh. While the refreshes are queuing up, the Count is still being incremented but not displayed.

Suspending and resuming

Once a thread is stopped, it cannot be restarted with the start() command, since stop() will terminate the execution of a thread. Instead you can pause the execution of a thread with the sleep() method. The thread will sleep for a certain period of time and then begin executing when the time limit is reached. But, this is not ideal if the thread needs to be started when a certain event occurs. In this case, the suspend() method allows a thread to temporarily cease executing and the resume() method allows the suspended thread to start again. The following applet shows the above example modified to suspend and resume the applet.

public class CounterThread2 extends Applet implements Runnable { Thread t; int Count; boolean suspended; public boolean mouseDown(Event e,int x, int y) { if(suspended) t.resume(); else t.suspend(); suspended = !suspended; return true; } ... }

CounterThread2 Example and Source code

Para realizar un seguimiento del estado actual del subprograma, suspendedse utiliza la variable booleana . Distinguir los diferentes estados de un subprograma es importante porque algunos métodos arrojarán excepciones si se llaman mientras están en el estado incorrecto. Por ejemplo, si el subprograma se ha iniciado y detenido, la ejecución del start()método generará una IllegalThreadStateExceptionexcepción.