JavaRush /Blog Java /Random-ES /No se puede estropear Java con un hilo: Parte I - Hilos
Viacheslav
Nivel 3

No se puede estropear Java con un hilo: Parte I - Hilos

Publicado en el grupo Random-ES

Introducción

El multiproceso se ha integrado en Java desde sus inicios. Entonces, echemos un vistazo rápido a de qué se trata el subproceso múltiple. No puedes arruinar Java con un hilo: Parte I - Hilos - 1Tomemos como punto de partida la lección oficial de Oracle: " Lección: La aplicación "¡Hola mundo!" ". Cambiemos un poco el código de nuestra aplicación Hello World a lo siguiente:
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. Guardemos este código en un archivo con un nombre que coincida con el nombre de la clase y la extensión .java. Compilemos usando la utilidad javac : javac HelloWorldApp.java después de eso, llamemos a nuestro código con algún parámetro, por ejemplo, Roger: java HelloWorldApp Roger No puedes arruinar Java con un hilo: Parte I - Hilos - 2nuestro código ahora tiene un defecto grave. Si no pasamos ningún argumento (es decir, simplemente ejecutamos java HelloWorldApp), obtendremos 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 un hilo llamado main. ¿Resulta que hay algún tipo de hilo en Java? Aquí es donde comienza nuestro viaje.

Java y hilos

Para comprender qué es un hilo, es necesario comprender cómo se inicia una aplicación Java. Cambiemos nuestro código de la siguiente manera:
class HelloWorldApp {
    public static void main(String[] args) {
		while (true) {
			//Do nothing
		}
	}
}
Ahora compilémoslo nuevamente usando javac. A continuación, por conveniencia, ejecutaremos nuestro código Java en una ventana separada. En Windows puedes hacer esto así: start java HelloWorldApp. Ahora, usando la utilidad jps , veamos qué información nos dirá Java: No puedes arruinar Java con un hilo: Parte I - Hilos - 3El primer número es el PID o Process ID, el identificador del proceso. ¿Qué es un proceso?
Процесс — это совокупность códigoа и данных, разделяющих общее виртуальное DIRECCIÓNное пространство.
Con la ayuda de procesos, la ejecución de diferentes programas se aísla entre sí: cada aplicación utiliza su propia área de memoria sin interferir con otros programas. Te aconsejo que leas el artículo con más detalle: " https://habr.com/post/164487/ ". Un proceso no puede existir sin subprocesos, por lo que si existe un proceso, existe al menos un subproceso en él. ¿Cómo sucede esto en Java? Cuando ejecutamos un programa Java, su ejecución comienza con el archivo main. En cierto modo ingresamos al programa, por lo que este método especial mainse llama punto de entrada o "punto de entrada". El método mainsiempre debe ser public static voidpara que la Máquina Virtual Java (JVM) pueda comenzar a ejecutar nuestro programa. Consulte "¿ Por qué el método principal de Java es estático? " para obtener más detalles. Resulta que el iniciador de Java (java.exe o javaw.exe) es una aplicación simple (aplicación C simple): carga varias DLL, que en realidad son la JVM. El iniciador de Java realiza un conjunto específico de llamadas a la interfaz nativa de Java (JNI). JNI es el mecanismo que une el mundo de la máquina virtual Java y el mundo de C++. Resulta que el iniciador no es la JVM, sino su cargador. Conoce los comandos correctos a ejecutar para iniciar la JVM. Sabe organizar todo el entorno necesario mediante llamadas JNI. Esta organización del entorno también incluye la creación de un hilo principal, que suele denominarse main. Para ver más claramente qué subprocesos viven en un proceso java, utilizamos el programa jvisualvm , que está incluido en el JDK. Conociendo el pid de un proceso, podemos abrir datos sobre él inmediatamente: jvisualvm --openpid айдипроцесса No puedes arruinar Java con un hilo: Parte I - Hilos - 4Curiosamente, cada hilo tiene su propia área separada en la memoria asignada para el proceso. Esta estructura de memoria se llama pila. Una pila consta de marcos. Un marco es el punto de llamada de un método, punto de ejecución. Un marco también se puede representar como StackTraceElement (consulte API de Java para StackTraceElement ). Puede leer más sobre la memoria asignada a cada hilo aquí . Si miramos la API de Java y buscamos la palabra Thread, veremos que hay una clase java.lang.Thread . Es esta clase la que representa una secuencia en Java, y es con ella con la que tenemos que trabajar. No puedes arruinar Java con un hilo: Parte I - Hilos - 5

java.lang.Thread

Un hilo en Java se representa como una instancia de la clase java.lang.Thread. Vale la pena comprender de inmediato que las instancias de la clase Thread en Java no son subprocesos en sí mismos. Esto es solo una especie de API para subprocesos de bajo nivel administrados por la JVM y el sistema operativo. Cuando iniciamos la JVM usando el iniciador de Java, crea un hilo principal con un nombre mainy varios hilos de servicio más. Como se indica en el JavaDoc de la clase Thread: When a Java Virtual Machine starts up, there is usually a single non-daemon thread Hay 2 tipos de threads: demonios y no demonios. Los subprocesos de demonio son subprocesos en segundo plano (subprocesos de servicio) que realizan algún trabajo en segundo plano. Este interesante término es una referencia al “demonio de Maxwell”, sobre el cual puedes leer más en el artículo de Wikipedia sobre “ demonios ”. Como se indica en la documentación, la JVM continúa ejecutando el programa (proceso) hasta:
  • El método Runtime.exit no se llama
  • Todos los subprocesos que no son demonios completaron su trabajo (tanto sin errores como con excepciones)
De ahí el detalle importante: los subprocesos del demonio se pueden terminar con cualquier comando que se ejecute. Por tanto, no se garantiza la integridad de los datos que contienen. Por lo tanto, los subprocesos de demonio son adecuados para algunas tareas de servicio. Por ejemplo, en Java existe un hilo que se encarga de procesar métodos de finalización o hilos relacionados con el Garbage Collector (GC). Cada hilo pertenece a algún grupo ( ThreadGroup ). Y los grupos pueden unirse entre sí, formando alguna 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 le permiten agilizar la gestión de los flujos y realizar un seguimiento de los mismos. Además de los grupos, los subprocesos tienen su propio controlador de excepciones. Veamos 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("Ocurrió un error: " + e.getMessage());
		}
	});
    System.out.println(2/0);
}
La división por cero provocará un error que será detectado por el controlador. Si no especifica el controlador usted mismo, funcionará la implementación del controlador predeterminado, que mostrará la pila de errores en StdError. Puede leer más en la revisión http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ ". Además, el hilo tiene una prioridad. Puede leer más sobre las prioridades en el artículo " Prioridad de subprocesos de Java en subprocesos múltiples ".

Creando un hilo

Como se indica en la documentación, tenemos 2 formas de crear un hilo. La primera es crear su heredero. 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, la tarea se inicia en el método runy el hilo se inicia en el método start. No deben confundirse, porque... Si ejecutamos el método rundirectamente, no se iniciará ningún hilo nuevo. Es el método startque le pide a la JVM que cree un nuevo hilo. La opción con un descendiente de Thread es mala porque incluimos Thread en la jerarquía de clases. La segunda desventaja es que estamos empezando a violar el principio de “Responsabilidad Única” SOLID, porque nuestra clase se vuelve simultáneamente responsable tanto de gestionar el hilo como de alguna tarea que debe realizarse en este hilo. ¿Cual es correcta? La respuesta está en el mismo método runque anulamos:
public void run() {
	if (target != null) {
		target.run();
	}
}
Aquí targethay algunos java.lang.Runnableque podemos pasar a Thread al crear una instancia de la clase. Por tanto, 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();
    }
}
También es Runnableuna interfaz funcional desde Java 1.8. Esto le permite escribir código de tarea para subprocesos de manera aún más hermosa:
public static void main(String []args){
	Runnable task = () -> {
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

Total

Entonces, espero que de esta historia quede claro qué es un flujo, cómo existen y qué operaciones básicas se pueden realizar con ellos. En la siguiente parte , vale la pena comprender cómo interactúan los hilos entre sí y cuál es su ciclo de vida. #viacheslav
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION