CodeGym/Blog Java/Random-ES/Mejor juntos: Java y la clase Thread. Parte I — Hilos de ...
John Squirrels
Nivel 41
San Francisco

Mejor juntos: Java y la clase Thread. Parte I — Hilos de ejecución

Publicado en el grupo Random-ES

Introducción

El subprocesamiento múltiple se incorporó a Java desde el principio. Por lo tanto, veamos brevemente esta cosa llamada subprocesos múltiples. Mejor juntos: Java y la clase Thread.  Parte I — Hilos de ejecución - 1Tomamos como punto de referencia la lección oficial de Oracle: " Lección: La aplicación "¡Hola mundo!" ". Modificaremos ligeramente el código de nuestro programa Hello World de la siguiente manera:
class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argses una matriz de parámetros de entrada que se pasan cuando se inicia el programa. Guarde este código en un archivo con un nombre que coincida con el nombre de la clase y tenga la extensión .java. Compílelo usando la utilidad javacjavac HelloWorldApp.java : . Luego, ejecutamos nuestro código con algún parámetro, por ejemplo, "Roger": java HelloWorldApp Roger Mejor juntos: Java y la clase Thread.  Parte I — Hilos de ejecución - 2Nuestro código actualmente tiene una falla grave. Si no pasa ningún argumento (es decir, ejecuta solo "java HelloWorldApp"), obtenemos un error:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
Se produjo una excepción (es decir, un error) en el subproceso denominado "principal". Entonces, ¿Java tiene hilos? Aquí es donde comienza nuestro viaje.

Java e hilos

Para comprender qué es un hilo, debe comprender cómo se inicia un programa Java. Cambiemos nuestro código de la siguiente manera:
class HelloWorldApp {
    public static void main(String[] args) {
		while (true) {
			// Do nothing
		}
	}
}
Ahora vamos a compilarlo de nuevo con javac. Para mayor comodidad, ejecutaremos nuestro código Java en una ventana separada. En Windows, esto se puede hacer así: start java HelloWorldApp. Ahora usaremos la utilidad jps para ver qué información nos puede dar Java: Mejor juntos: Java y la clase Thread.  Parte I — Hilos de ejecución - 3El primer número es el PID o ID del proceso. ¿Qué es un proceso?
A process is a combination of code and data sharing a common virtual address space.
Con los procesos, los diferentes programas se aíslan entre sí mientras se ejecutan: cada aplicación usa su propia área en la memoria sin interferir con otros programas. Para obtener más información, recomiendo leer este tutorial: Procesos y subprocesos . Un proceso no puede existir sin un subproceso, por lo que si existe un proceso, entonces tiene al menos un subproceso. Pero, ¿cómo sucede esto en Java? Cuando iniciamos un programa Java, la ejecución comienza con el mainmétodo. Es como si estuviéramos entrando en el programa, por lo que este mainmétodo especial se denomina punto de entrada. El mainmétodo debe ser siempre "public static void", para que la máquina virtual Java (JVM) pueda empezar a ejecutar nuestro programa. Para obtener más información, ¿Por qué el método principal de Java es estático?. Resulta que el iniciador de Java (java.exe o javaw.exe) es una aplicación C simple: carga las distintas DLL que en realidad componen la JVM. El iniciador de Java realiza un conjunto específico de llamadas de interfaz nativa de Java (JNI). JNI es un mecanismo para conectar el mundo de la máquina virtual Java con el mundo de C++. Entonces, el iniciador no es la JVM en sí, sino un mecanismo para cargarla. Conoce los comandos correctos a ejecutar para iniciar la JVM. Sabe cómo utilizar las llamadas JNI para configurar el entorno necesario. La configuración de este entorno incluye la creación del hilo principal, que se llama "principal", por supuesto. Para ilustrar mejor qué subprocesos existen en un proceso de Java, usamos jvisualvmherramienta, que se incluye con el JDK. Conociendo el pid de un proceso, podemos ver inmediatamente información sobre ese proceso: jvisualvm --openpid <process id> Mejor juntos: Java y la clase Thread.  Parte I — Hilos de ejecución - 4Curiosamente, cada subproceso tiene su propia área separada en la memoria asignada al proceso. Esta estructura de memoria se llama pila. Una pila consta de marcos. Un marco representa la activación de un método (una llamada de método sin terminar). Un marco también se puede representar como StackTraceElement (consulte la API de Java para StackTraceElement ). Puede encontrar más información sobre la memoria asignada a cada subproceso en la discusión aquí: "¿ Cómo asigna Java (JVM) la pila para cada subproceso ?". Si observa la API de Java y busca la palabra "Thread", encontrará java.lang.Threadclase. Esta es la clase que representa un hilo en Java, y necesitaremos trabajar con ella. Mejor juntos: Java y la clase Thread.  Parte I — Hilos de ejecución - 5

java.lang.Subproceso

En Java, un hilo está representado por una instancia de la java.lang.Threadclase. Debe comprender de inmediato que las instancias de la clase Thread no son en sí mismas hilos de ejecución. Esta es solo una especie de API para los subprocesos de bajo nivel administrados por la JVM y el sistema operativo. Cuando iniciamos la JVM usando el iniciador de Java, crea un mainsubproceso llamado "principal" y algunos otros subprocesos de mantenimiento. Como se indica en el JavaDoc para la clase Thread: When a Java Virtual Machine starts up, there is usually a single non-daemon thread. Hay 2 tipos de hilos: demonios y no demonios. Los subprocesos de daemon son subprocesos de fondo (mantenimiento) que realizan algún trabajo en segundo plano. La palabra "demonio" se refiere al demonio de Maxwell. Puedes aprender más en este artículo de Wikipedia . Como se indica en la documentación, la JVM continúa ejecutando el programa (proceso) hasta que:
  • El método Runtime.exit() se llama
  • Todos los subprocesos NO demonio terminan su trabajo (sin errores o con excepciones lanzadas)
De esto se desprende un detalle importante: los subprocesos del daemon se pueden terminar en cualquier punto. Como resultado, no hay garantías sobre la integridad de sus datos. En consecuencia, los subprocesos de daemon son adecuados para ciertas tareas de limpieza. Por ejemplo, Java tiene un subproceso que es responsable de procesar finalize()llamadas a métodos, es decir, subprocesos relacionados con el Recolector de Basura (gc). Cada subproceso es parte de un grupo ( ThreadGroup ). Y los grupos pueden formar parte de otros grupos, formando una cierta jerarquía o estructura.
public static void main(String[] args) {
	Thread currentThread = Thread.currentThread();
	ThreadGroup threadGroup = currentThread.getThreadGroup();
	System.out.println("Thread: " + currentThread.getName());
	System.out.println("Thread Group: " + threadGroup.getName());
	System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
Los grupos ponen orden en la gestión de subprocesos. Además de los grupos, los subprocesos tienen su propio controlador de excepciones. Echa un vistazo a un ejemplo:
public static void main(String[] args) {
	Thread th = Thread.currentThread();
	th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
		@Override
		public void uncaughtException(Thread t, Throwable e) {
			System.out.println("An error occurred: " + e.getMessage());
		}
	});
    System.out.println(2/0);
}
La división por cero provocará un error que será detectado por el controlador. Si no especifica su propio controlador, la JVM invocará el controlador predeterminado, que generará el seguimiento de la pila de la excepción a StdError. Cada hilo también tiene una prioridad. Puede leer más sobre las prioridades en este artículo: Java Thread Priority in Multithreading .

Creando un hilo

Como se indica en la documentación, tenemos 2 formas de crear un hilo. La primera forma es crear su propia subclase. Por ejemplo:
public class HelloWorld{
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello, World!");
        }
    }

    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();
    }
}
Como puede ver, el trabajo de la tarea ocurre en el run()método, pero el subproceso en sí se inicia en el start()método. No confunda estos métodos: si llamamos un()directamente al método r, no se iniciará ningún hilo nuevo. Es el start()método que le pide a la JVM que cree un nuevo hilo. Esta opción en la que heredamos Thread ya es mala porque incluimos Thread en nuestra jerarquía de clases. El segundo inconveniente es que estamos empezando a violar el principio de "responsabilidad única". Es decir, nuestra clase es simultáneamente responsable de controlar el hilo y de alguna tarea a realizar en este hilo. ¿Cuál es el camino correcto? La respuesta se encuentra en el mismo run()método, que anulamos:
public void run() {
	if (target != null) {
		target.run();
	}
}
Aquí targethay algunos java.lang.Runnableque podemos pasar al crear una instancia de la clase Thread. Esto significa que podemos hacer esto:
public class HelloWorld{
    public static void main(String[] args) {
        Runnable task = new Runnable() {
            public void run() {
                System.out.println("Hello, World!");
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Runnabletambién ha sido una interfaz funcional desde Java 1.8. Esto hace posible escribir un código aún más hermoso para la tarea de un hilo:
public static void main(String[] args) {
	Runnable task = () -> {
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

Conclusión

Espero que esta discusión aclare qué es un subproceso, cómo surgen los subprocesos y qué operaciones básicas se pueden realizar con los subprocesos. En la siguiente parte , intentaremos comprender cómo los subprocesos interactúan entre sí y exploraremos el ciclo de vida del subproceso. Mejor juntos: Java y la clase Thread. Parte II — Sincronización Mejor juntos: Java y la clase Thread. Parte III — Interacción Mejor juntos: Java y la clase Thread. Parte IV — Callable, Future y amigos Mejor juntos: Java y la clase Thread. Parte V — Ejecutor, ThreadPool, Fork/Join Mejor juntos: Java y la clase Thread. Parte VI — ¡Dispara!
Comentarios
  • Populares
  • Nuevas
  • Antiguas
Debes iniciar sesión para dejar un comentario
Esta página aún no tiene comentarios