JavaRush /Blog Java /Random-ES /Las 50 preguntas y respuestas principales de las entrevis...

Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core. parte 3

Publicado en el grupo Random-ES
Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core. Parte 1 Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core. Parte 2

subprocesos múltiples

37. ¿Cómo crear un nuevo hilo (flujo) en Java?

De una forma u otra, la creación se produce mediante el uso de la clase Thread. Pero puede haber opciones aquí...
  1. heredamos dejava.lang.Thread
  2. Implementamos una interfaz cuyo objeto acepta una clase java.lang.Runnableconstructora.Thread
Hablemos de cada uno de ellos.

Heredamos de la clase Thread.

Para que esto funcione, en nuestra clase heredamos de java.lang.Thread. Contiene metanfetamina run(), que es exactamente lo que necesitamos. Toda la vida y lógica del nuevo hilo estará en este método. Este es un tipo de mainmétodo para un hilo nuevo. Después de esto solo queda crear un objeto de nuestra clase y ejecutar el método start(), que creará un nuevo hilo y ejecutará la lógica escrita en él. Miremos:
/**
* Пример того, Cómo создавать треды путем наследования {@link Thread} класса.
*/
class ThreadInheritance extends Thread {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance threadInheritance1 = new ThreadInheritance();
       ThreadInheritance threadInheritance2 = new ThreadInheritance();
       ThreadInheritance threadInheritance3 = new ThreadInheritance();
       threadInheritance1.start();
       threadInheritance2.start();
       threadInheritance3.start();
   }
}
La salida a la consola será así:

Thread-1
Thread-0
Thread-2
Es decir, incluso aquí vemos que los subprocesos no se ejecutan por turno, sino como lo decidió la JVM)

Implementando la interfaz Runnable

Si está en contra de la herencia y/o ya hereda una de las otras clases, puede usar el archivo java.lang.Runnable. Aquí en nuestra clase implementamos esta interfaz e implementamos el método run(), como fue en ese ejemplo. Sólo necesitas crear más objetos Thread. Parecería que más líneas son peores. Pero sabemos lo dañina que es la herencia y que es mejor evitarla a toda costa ;) Veamos:
/**
* Пример того, Cómo создавать треды из интерфейса {@link Runnable}.
* Здесь проще простого - реализуем этот интерфейс и потом передаем в конструктор
* экземпляр реализуемого un objetoа.
*/
class ThreadInheritance implements Runnable {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance runnable1 = new ThreadInheritance();
       ThreadInheritance runnable2 = new ThreadInheritance();
       ThreadInheritance runnable3 = new ThreadInheritance();

       Thread threadRunnable1 = new Thread(runnable1);
       Thread threadRunnable2 = new Thread(runnable2);
       Thread threadRunnable3 = new Thread(runnable3);

       threadRunnable1.start();
       threadRunnable2.start();
       threadRunnable3.start();
   }
}
Y el resultado de la ejecución:

Thread-0
Thread-1
Thread-2

38. ¿Cuál es la diferencia entre un proceso y un hilo?

Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 3 - 1Existen las siguientes diferencias entre un proceso y un hilo:
  1. Un programa en ejecución se denomina proceso, mientras que un subproceso es un subconjunto de un proceso.
  2. Los procesos son independientes, mientras que los subprocesos son un subconjunto de un proceso.
  3. Los procesos tienen diferentes espacios de direcciones en la memoria, mientras que los subprocesos contienen un espacio de direcciones común.
  4. El cambio de contexto es más rápido entre subprocesos en comparación con los procesos.
  5. La comunicación entre procesos es más lenta y costosa que la comunicación entre subprocesos.
  6. Cualquier cambio en el proceso principal no afecta al proceso secundario, mientras que los cambios en el subproceso principal pueden afectar al subproceso secundario.

39. ¿Cuáles son las ventajas del subproceso múltiple?

Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 3 - 2
  1. El subproceso múltiple permite que una aplicación/programa responda siempre a la entrada incluso si ya está ejecutando algunas tareas en segundo plano;
  2. El multiproceso le permite completar tareas más rápido porque los subprocesos se ejecutan de forma independiente;
  3. Los subprocesos múltiples proporcionan una mejor utilización de la caché porque los subprocesos comparten recursos de memoria comunes;
  4. Los subprocesos múltiples reducen la cantidad de servidor requerido porque un servidor puede ejecutar varios subprocesos simultáneamente.

40. ¿Cuáles son los estados en el ciclo de vida de un hilo?

Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 3 - 3
  1. Nuevo: en este estado, Threadse crea un objeto de clase utilizando el nuevo operador, pero el hilo no existe. El hilo no comienza hasta que llamamos al start().
  2. Ejecutable: en este estado, el hilo está listo para ejecutarse después de llamar al método. comenzar(). Sin embargo, el programador de subprocesos aún no lo ha seleccionado.
  3. En ejecución: en este estado, el programador de subprocesos selecciona un subproceso del estado listo y lo ejecuta.
  4. Esperando/Bloqueado: en este estado, el subproceso no se está ejecutando pero aún está activo o esperando a que se complete otro subproceso.
  5. Muerto/Terminado: cuando el método sale, run()el hilo está en un estado terminado o muerto.

41. ¿Es posible iniciar un hilo dos veces?

No, no podemos reiniciar el hilo porque una vez que se inicia y ejecuta el hilo, pasa al estado Muerto. Entonces, si intentamos ejecutar el hilo dos veces, arrojará una runtimeException " java.lang.IllegalThreadStateException ". Miremos:
class DoubleStartThreadExample extends Thread {

   /**
    * Имитируем работу треда
    */
   public void run() {
	// что-то происходит. Для нас не существенно на этом этапе
   }

   /**
    * Запускаем тред дважды
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
Tan pronto como el trabajo llegue al segundo inicio del mismo hilo, habrá una excepción. Pruébelo usted mismo;) es mejor ver una vez que escuchar cien veces.

42. ¿Qué pasa si llamas al método run() directamente sin llamar al método start()?

Sí, run()por supuesto que puedes llamar a un método, pero esto no creará un nuevo hilo ni lo ejecutará como un hilo separado. En este caso, es un objeto simple que llama a un método simple. Si hablamos del método start(), entonces es otra cuestión. Al iniciar este método, runtimelanza uno nuevo y, a su vez, ejecuta nuestro método ;) Si no me crees, pruébalo:
class ThreadCallRunExample extends Thread {

   public void run() {
       for (int i = 0; i < 5; i++) {
           System.out.print(i);
       }
   }

   public static void main(String args[]) {
       ThreadCallRunExample runExample1 = new ThreadCallRunExample();
       ThreadCallRunExample runExample2 = new ThreadCallRunExample();

       // просто будут вызваны в потоке main два метода, один за другим.
       runExample1.run();
       runExample2.run();
   }
}
Y la salida a la consola será así:

0123401234
Se puede ver que no se creó ningún hilo. Todo funcionó como una clase normal. Primero funcionó el método de la primera clase, luego el segundo.

43. ¿Qué es un hilo de demonio?

Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 3 - 4El hilo de demonio (en lo sucesivo, hilo de demonio) es un hilo que realiza tareas en segundo plano en relación con otro hilo. Es decir, su trabajo es realizar tareas auxiliares que deben realizarse sólo en conjunto con otro hilo (principal). Hay muchos subprocesos de demonios que funcionan automáticamente, como el recolector de basura, el finalizador, etc.

¿Por qué Java cierra el hilo del demonio?

El único propósito de un hilo de demonio es proporcionar servicios al hilo de usuario para tareas de soporte en segundo plano. Por lo tanto, si el hilo principal se ha completado, el tiempo de ejecución cierra automáticamente todos sus hilos de demonio.

Métodos para trabajar en la clase Thread.

La clase java.lang.Threadproporciona dos métodos para trabajar con el demonio de subprocesos:
  1. public void setDaemon(boolean status)- indica que este será un hilo de demonio. El valor predeterminado es false, lo que significa que se crearán subprocesos que no sean demonios a menos que se especifiquen por separado.
  2. public boolean isDaemon()- Básicamente, este es un captador de la variable daemonque configuramos usando el método anterior.
Ejemplo:
class DaemonThreadExample extends Thread {

   public void run() {
       // Проверяет, демон ли этот поток o нет
       if (Thread.currentThread().isDaemon()) {
           System.out.println("daemon thread");
       } else {
           System.out.println("user thread");
       }
   }

   public static void main(String[] args) {
       DaemonThreadExample thread1 = new DaemonThreadExample();
       DaemonThreadExample thread2 = new DaemonThreadExample();
       DaemonThreadExample thread3 = new DaemonThreadExample();

       // теперь thread1 - поток-демон.
       thread1.setDaemon(true);

       System.out.println("демон?.. " + thread1.isDaemon());
       System.out.println("демон?.. " + thread2.isDaemon());
       System.out.println("демон?.. " + thread3.isDaemon());

       thread1.start();
       thread2.start();
       thread3.start();
   }
}
Salida de consola:

демон?.. true
демон?.. false
демон?.. false
daemon thread
user thread
user thread
De la salida vemos que dentro del hilo mismo, usando un currentThread()método estático, podemos averiguar qué hilo es, por un lado, por otro lado, si tenemos una referencia al objeto de este hilo, podemos averiguarlo. directamente de él. Esto proporciona la flexibilidad necesaria en la configuración.

44. ¿Es posible convertir un hilo en demonio después de haber sido creado?

No. Si hace esto, generará una excepción IllegalThreadStateException. Por lo tanto, solo podemos crear un hilo de demonio antes de que comience. Ejemplo:
class SetDaemonAfterStartExample extends Thread {

   public void run() {
       System.out.println("Working...");
   }

   public static void main(String[] args) {
       SetDaemonAfterStartExample afterStartExample = new SetDaemonAfterStartExample();
       afterStartExample.start();

       // здесь будет выброшено исключение
       afterStartExample.setDaemon(true);
   }
}
Salida de consola:

Working...
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.setDaemon(Thread.java:1359)
	at SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

45. ¿Qué es un gancho de cierre?

Shutdownhook es un subproceso que se llama implícitamente antes de que se apague la JVM (máquina virtual Java). Entonces podemos usarlo para limpiar un recurso o guardar el estado cuando la Máquina Virtual Java se apaga de manera normal o repentina. Podemos agregar shutdown hookusando el siguiente método:
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Como se muestra en el ejemplo:
/**
* Программа, которая показывает Cómo запустить shutdown hook тред,
* который выполнится аккурат до окончания работы JVM
*/
class ShutdownHookThreadExample extends Thread {

   public void run() {
       System.out.println("shutdown hook задачу выполнил");
   }

   public static void main(String[] args) {

       Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());

       System.out.println("Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.");
       try {
           Thread.sleep(60000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}
Salida de consola:

Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.
shutdown hook задачу выполнил

46. ​​¿Qué es la sincronización?

La sincronización en Java es la capacidad de controlar el acceso de múltiples subprocesos a cualquier recurso compartido. Cuando varios subprocesos intentan realizar la misma tarea, existe la posibilidad de obtener un resultado erróneo, por lo que para superar este problema, Java utiliza la sincronización, por lo que solo un subproceso puede funcionar a la vez. La sincronización se puede lograr de tres maneras:
  • Método de sincronización
  • Sincronizando un bloque específico
  • Sincronización estática

Sincronización de métodos

El método sincronizado se utiliza para bloquear un objeto para cualquier recurso compartido. Cuando un hilo llama a un método sincronizado, automáticamente adquiere un bloqueo en ese objeto y lo libera cuando el hilo completa su tarea. Para que funcione, debe agregar la palabra clave sincronizada . Veamos cómo funciona esto con un ejemplo:
/**
* Пример, где мы синхронизируем метод. То есть добавляем ему слово synchronized.
* Есть два писателя, которые хотят использовать один принтер. Они подготовo свои поэмы
* И конечно же не хотят, чтоб их поэмы перемешались, а хотят, чтоб работа была сделана по * * * очереди для каждого из них
*/
class Printer {

   synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {
       // один un objeto для двух тредов
       Printer printer  = new Printer();

       // создаем два треда
       Writer1 writer1 = new Writer1(printer);
       Writer2 writer2 = new Writer2(printer);

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

/**
* Писатель номер 1, который пишет свою поэму.
*/
class Writer1 extends Thread {
   Printer printer;

   Writer1(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<string> poem = Arrays.asList("Я ", this.getName(), " Пишу", " Письмо");
       printer.print(poem);
   }

}

/**
* Писатель номер 2, который пишет свою поэму.
*/
class Writer2 extends Thread {
   Printer printer;

   Writer2(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<String> poem = Arrays.asList("Не Я ", this.getName(), " Не пишу", " Не Письмо");
       printer.print(poem);
   }
}
Y la salida a la consola:

Я Thread-0 Пишу Письмо
Не Я Thread-1 Не пишу Не Письмо

Bloque de sincronización

Se puede utilizar un bloque sincronizado para realizar la sincronización en cualquier recurso de método específico. Digamos que en un método grande (sí, sí, no puedes escribir esas cosas, pero a veces sucede) necesitas sincronizar solo una pequeña parte, por alguna razón. Si pones todos los códigos de un método en un bloque sincronizado, funcionará igual que un método sincronizado. La sintaxis se ve así:
synchronized (“un objeto для блокировки”) {
   // сам código, который нужно защитить
}
Para no repetir el ejemplo anterior, crearemos subprocesos a través de clases anónimas, es decir, implementaremos inmediatamente la interfaz Runnable.
/**
* Вот Cómo добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

   void print(List<String> wordsToPrint) {
       synchronized (this) {
           wordsToPrint.forEach(System.out::print);
       }
       System.out.println();
   }

   public static void main(String args[]) {
       // один un objeto для двух тредов
       Printer printer = new Printer();

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

}
y salida a la consola

Я Writer1 Пишу Письмо
Не Я Writer2 Не пишу Не Письмо

Sincronización estática

Si sincroniza un método estático, el bloqueo estará en la clase, no en el objeto. En este ejemplo, aplicamos la palabra clave sincronizada a un método estático para realizar una sincronización estática:
/**
* Вот Cómo добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

   static synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               Printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               Printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}
y la salida a la consola:

Не Я Writer2 Не пишу Не Письмо
Я Writer1 Пишу Письмо

47. ¿Qué es una variable volátil?

La palabra clave volatilese utiliza en la programación de subprocesos múltiples para proporcionar seguridad a los subprocesos porque una modificación de una variable mutable es visible para todos los demás subprocesos, por lo que un subproceso puede utilizar una variable a la vez. Usando la palabra clave, volatilepuede garantizar que la variable será segura para subprocesos y se almacenará en la memoria compartida, y los subprocesos no la llevarán a su caché. Cómo se ve?
private volatile AtomicInteger count;
Simplemente agregamos a la variable volatile. Pero esto no significa seguridad total para los subprocesos... Después de todo, es posible que las operaciones no sean atómicas en una variable. Pero puedes usar Atomicclases que realicen la operación de forma atómica, es decir, en una sola ejecución por parte del procesador. Muchas de estas clases se pueden encontrar en el paquete java.util.concurrent.atomic.

48. ¿Qué es el punto muerto?

El punto muerto en Java es parte del subproceso múltiple. Un punto muerto puede ocurrir en una situación en la que un subproceso está esperando un bloqueo de objeto adquirido por otro subproceso, y un segundo subproceso está esperando un bloqueo de objeto adquirido por el primer subproceso. Por lo tanto, estos dos hilos se esperan el uno al otro y no continuarán ejecutando su código. Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 3 - 5Veamos un ejemplo en el que hay una clase que implementa Runnable. Acepta dos recursos en su constructor. Dentro del método run(), toma el bloqueo uno por uno, por lo que si creas dos objetos de esta clase y transfieres los recursos en diferentes órdenes, puedes encontrarte fácilmente con un bloqueo:
class DeadLock {

   public static void main(String[] args) {
       final Integer r1 = 10;
       final Integer r2 = 15;

       DeadlockThread threadR1R2 = new DeadlockThread(r1, r2);
       DeadlockThread threadR2R1 = new DeadlockThread(r2, r1);

       new Thread(threadR1R2).start();
       new Thread(threadR2R1).start();
   }
}

/**
* Класс, который принимает два ресурса.
*/
class DeadlockThread implements Runnable {

   private final Integer r1;
   private final Integer r2;

   public DeadlockThread(Integer r1, Integer r2) {
       this.r1 = r1;
       this.r2 = r2;
   }

   @Override
   public void run() {
       synchronized (r1) {
           System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r1);

           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }

           synchronized (r2) {
               System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r2);
           }
       }
   }
}
Salida de consola:

Первый тред захватил первый ресурс
Второй тред захватывает второй ресурс

49. ¿Cómo evitar un punto muerto?

Basándonos en lo que sabemos cómo se produce un punto muerto, podemos sacar algunas conclusiones...
  • Como se muestra en el ejemplo anterior, el punto muerto se debió al anidamiento de cerraduras. Es decir, dentro de una cerradura hay otra o más. Esto se puede evitar de la siguiente manera: en lugar de anidar, debe agregar una nueva abstracción en la parte superior y asignar el bloqueo a un nivel superior, y eliminar los bloqueos anidados.
  • Cuanto más bloqueo haya, mayores serán las posibilidades de que se produzca un punto muerto. Por lo tanto, cada vez que agrega un candado, debe pensar si realmente es necesario y si se puede evitar agregar uno nuevo.
  • Usos Thread.join(). También se puede producir un punto muerto cuando un hilo está esperando a otro. Para evitar este problema, podría considerar establecer un límite de tiempo para join()el método.
  • Si tenemos un hilo, no habrá punto muerto;)

50. ¿Qué es una condición de carrera?

Si en carreras reales los autos funcionan, entonces, en la terminología de carreras de subprocesos múltiples, los subprocesos funcionan en las carreras. ¿Pero por qué? Hay dos hilos que se están ejecutando y que pueden tener acceso al mismo objeto. Y pueden intentar actualizar el estado al mismo tiempo. Hasta aquí todo está claro ¿verdad? Entonces, los subprocesos funcionan en paralelo real (si hay más de un núcleo en el procesador) o en paralelo condicional, cuando el procesador asigna un corto período de tiempo. Y no podemos controlar estos procesos, por lo que no podemos garantizar que cuando un hilo lea datos de un objeto, tendrá tiempo de cambiarlos ANTES de que otro hilo lo haga. Problemas como este ocurren cuando se lleva a cabo esta combinación de probar y actuar. ¿Qué significa? Por ejemplo, tenemos ifuna expresión en cuyo cuerpo cambia la condición misma, es decir:
int z = 0;

// проверь
if (z < 5) {
//действуй
   z = z + 5;
}
Por lo tanto, puede surgir una situación en la que dos subprocesos ingresan simultáneamente a este bloque de código en un momento en que z todavía es igual a cero y juntos cambian este valor. Y al final no obtendremos el valor esperado de 5, sino 10. ¿Cómo evitarlo? Debe bloquear antes y después de la ejecución. Es decir, para que el primer hilo ingrese al bloque if, realice todas las acciones, cámbielo zy solo entonces déle al siguiente hilo la oportunidad de hacerlo. Pero el siguiente hilo no entrará en el bloque if, ya zque ya será igual a 5:
// получить блокировку для z
if (z < 5) {
   z = z + 5;
}
// выпустить из блокировки z
===================================================

En lugar de salida

Quiero agradecer a todos los que leyeron hasta el final. ¡Fue un largo viaje y lo lograste! Puede que no todo esté claro. Esto esta bien. Tan pronto como comencé a aprender Java, no podía entender qué era una variable estática. Pero nada, me quedé dormido con este pensamiento, leí algunas fuentes más y finalmente lo entendí. Prepararse para una entrevista es más una cuestión académica que práctica. Por lo tanto, antes de cada entrevista, es necesario repetir y refrescar la memoria de cosas que quizás no utilices con mucha frecuencia.

Y como siempre enlaces útiles:

Gracias a todos por leer, nos vemos pronto) Mi perfil en GitHub
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION