JavaRush /Blogue Java /Random-PT /As 50 principais perguntas e respostas da entrevista sobr...
Roman Beekeeper
Nível 35

As 50 principais perguntas e respostas da entrevista sobre Java Core. Parte 3

Publicado no grupo Random-PT
As 50 principais perguntas e respostas da entrevista sobre Java Core. Parte 1 As 50 principais perguntas e respostas da entrevista sobre Java Core. Parte 2

Multithreading

37. Como criar um novo thread (fluxo) em Java?

De uma forma ou de outra, a criação ocorre através do uso da classe Thread. Mas pode haver opções aqui...
  1. Nós herdamos dejava.lang.Thread
  2. Implementamos uma interface cujo objeto aceita uma classe java.lang.RunnableconstrutoraThread
Vamos falar sobre cada um deles.

Herdamos da classe Thread

Para fazer isso funcionar, em nossa classe herdamos de java.lang.Thread. Ele contém metanfetamina run(), que é exatamente o que precisamos. Toda a vida e lógica do novo thread estarão neste método. Este é um tipo de mainmétodo para um novo tópico. Depois disso, resta criar um objeto da nossa classe e executar o método start(), que criará um novo thread e executará a lógica escrita nele. Vamos olhar:
/**
* Пример того, How создавать треды путем наследования {@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();
   }
}
A saída para o console será assim:

Thread-1
Thread-0
Thread-2
Ou seja, mesmo aqui vemos que os threads não são executados alternadamente, mas conforme a JVM decidiu)

Implementando a interface Runnable

Se você é contra a herança e/ou já herda uma das outras classes, você pode usar o java.lang.Runnable. Aqui na nossa classe implementamos essa interface e implementamos o método run(), como foi naquele exemplo. Você só precisa criar mais objetos Thread. Parece que mais linhas são piores. Mas sabemos o quão prejudicial é a herança e que é melhor evitá-la a todo custo ;) Vejamos:
/**
* Пример того, How создавать треды из интерфейса {@link Runnable}.
* Здесь проще простого - реализуем этот интерфейс и потом передаем в конструктор
* экземпляр реализуемого an object.
*/
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();
   }
}
E o resultado da execução:

Thread-0
Thread-1
Thread-2

38. Qual é a diferença entre um processo e um thread?

As 50 principais perguntas e respostas da entrevista sobre Java Core.  Parte 3 - 1Existem as seguintes diferenças entre um processo e um thread:
  1. Um programa em execução é chamado de processo, enquanto Thread é um subconjunto de um processo.
  2. Os processos são independentes, enquanto os threads são um subconjunto de um processo.
  3. Os processos possuem espaços de endereçamento diferentes na memória, enquanto os threads contêm um espaço de endereçamento comum.
  4. A troca de contexto é mais rápida entre threads em comparação com processos.
  5. A comunicação entre processos é mais lenta e mais cara que a comunicação entre threads.
  6. Quaisquer alterações no processo pai não afetam o processo filho, enquanto as alterações no thread pai podem afetar o thread filho.

39. Quais são as vantagens do multithreading?

As 50 principais perguntas e respostas da entrevista sobre Java Core.  Parte 3 - 2
  1. O multithreading permite que um aplicativo/programa sempre responda à entrada, mesmo que já esteja executando algumas tarefas em segundo plano;
  2. O multithreading permite concluir tarefas mais rapidamente porque os threads são executados de forma independente;
  3. O multithreading fornece melhor utilização do cache porque os threads compartilham recursos de memória comuns;
  4. O multithreading reduz a quantidade de servidores necessários porque um servidor pode executar vários threads simultaneamente.

40. Quais são os estados do ciclo de vida de um thread?

As 50 principais perguntas e respostas da entrevista sobre Java Core.  Parte 3 - 3
  1. Novo: Neste estado, um objeto de classe Threadé criado usando o operador new, mas o thread não existe. O thread não inicia até chamarmos o start().
  2. Executável: neste estado, o thread está pronto para ser executado após chamar o método começar(). No entanto, ainda não foi selecionado pelo agendador de threads.
  3. Em execução: neste estado, o agendador de threads seleciona um thread do estado pronto e ele é executado.
  4. Aguardando/Bloqueado: Neste estado, o thread não está em execução, mas ainda está ativo ou aguardando a conclusão de outro thread.
  5. Morto/Terminado: Quando o método termina, run()o thread está em um estado finalizado ou morto.

41. É possível iniciar um tópico duas vezes?

Não, não podemos reiniciar o thread porque, uma vez iniciado e executado, ele entra no estado Dead. Portanto, se tentarmos executar o thread duas vezes, ele lançará uma runtimeException " java.lang.IllegalThreadStateException ". Vamos olhar:
class DoubleStartThreadExample extends Thread {

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

   /**
    * Запускаем тред дважды
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
Assim que o trabalho atingir o segundo início do mesmo thread, haverá uma exceção. Experimente você mesmo ;) é melhor ver uma vez do que ouvir cem vezes.

42. E se você chamar o método run() diretamente, sem chamar o método start()?

Sim, run()claro que você pode chamar um método, mas isso não criará um novo thread e o executará como um thread separado. Neste caso, é um objeto simples que chama um método simples. Se estamos falando sobre o método start(), então é uma questão diferente. Ao lançar este método, runtimeele lança um novo e este, por sua vez, executa o nosso método ;) Se você não acredita em mim, experimente:
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();
   }
}
E a saída para o console será assim:

0123401234
Pode-se observar que nenhum thread foi criado. Tudo funcionou como uma aula normal. Primeiro o método da primeira classe funcionou, depois o segundo.

43. O que é um thread daemon?

As 50 principais perguntas e respostas da entrevista sobre Java Core.  Parte 3 - 4Thread daemon (doravante denominado thread daemon) é um thread que executa tarefas em segundo plano em relação a outro thread. Ou seja, sua função é realizar tarefas auxiliares que precisam ser realizadas apenas em conjunto com outra thread (principal). Existem muitos threads daemon que são executados automaticamente, como Garbage Collector, finalizador, etc.

Por que Java fecha o thread do daemon?

O único propósito de um thread daemon é fornecer serviços ao thread do usuário para tarefas de suporte em segundo plano. Portanto, se o thread principal for concluído, o tempo de execução fechará automaticamente todos os seus threads daemon.

Métodos para trabalhar na classe Thread

A classe java.lang.Threadfornece dois métodos para trabalhar com o daemon de thread:
  1. public void setDaemon(boolean status)- indica que este será um thread daemon. O padrão é false, o que significa que threads não-daemon serão criados, a menos que sejam especificados separadamente.
  2. public boolean isDaemon()- essencialmente, este é um getter para a variável daemonque definimos usando o método anterior.
Exemplo:
class DaemonThreadExample extends Thread {

   public void run() {
       // Проверяет, демон ли этот поток or нет
       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();
   }
}
Saída do console:

демон?.. true
демон?.. false
демон?.. false
daemon thread
user thread
user thread
Pela saída vemos que dentro do próprio thread, usando um currentThread()método estático, podemos descobrir qual thread é por um lado, por outro lado, se tivermos uma referência ao objeto deste thread, podemos descobrir diretamente dele. Isto proporciona a flexibilidade necessária na configuração.

44. É possível transformar um thread em daemon depois de criado?

Não. Se você fizer isso, lançará uma exceção IllegalThreadStateException. Portanto, só podemos criar um thread daemon antes de começar. Exemplo:
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);
   }
}
Saída do console:

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

45. O que é um gancho de desligamento?

Shutdownhook é um thread que é chamado implicitamente antes do encerramento da JVM (Java Virtual Machine). Portanto, podemos usá-lo para limpar um recurso ou salvar o estado quando a Java Virtual Machine for desligada normalmente ou repentinamente. Podemos adicionar shutdown hookusando o seguinte método:
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Conforme mostrado no exemplo:
/**
* Программа, которая показывает How запустить 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();
       }
   }
}
Saída do console:

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

46. ​​​​O que é sincronização?

A sincronização em Java é a capacidade de controlar o acesso de vários threads a qualquer recurso compartilhado. Quando vários threads tentam realizar a mesma tarefa, existe a possibilidade de um resultado errôneo, portanto, para superar esse problema, Java utiliza sincronização, devido à qual apenas um thread pode funcionar por vez. A sincronização pode ser alcançada de três maneiras:
  • Método de sincronização
  • Ao sincronizar um bloco específico
  • Sincronização estática

Sincronização de método

O método sincronizado é usado para bloquear um objeto para qualquer recurso compartilhado. Quando um thread chama um método sincronizado, ele adquire automaticamente um bloqueio naquele objeto e o libera quando o thread conclui sua tarefa. Para que funcione, você precisa adicionar a palavra-chave sincronizada . Vamos ver como isso funciona com um exemplo:
/**
* Пример, где мы синхронизируем метод. То есть добавляем ему слово synchronized.
* Есть два писателя, которые хотят использовать один принтер. Они подготовor свои поэмы
* И конечно же не хотят, чтоб их поэмы перемешались, а хотят, чтоб работа была сделана по * * * очереди для каждого из них
*/
class Printer {

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

   public static void main(String args[]) {
       // один an object для двух тредов
       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);
   }
}
E a saída para o console:

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

Bloco de sincronização

Um bloco sincronizado pode ser usado para realizar a sincronização em qualquer recurso de método específico. Digamos que em um método grande (sim, sim, você não pode escrever essas coisas, mas às vezes acontece) você precisa sincronizar apenas uma pequena parte, por algum motivo. Se você colocar todos os códigos de um método em um bloco sincronizado, ele funcionará da mesma forma que um método sincronizado. A sintaxe é semelhante a esta:
synchronized (“an object для блокировки”) {
   // сам code, который нужно защитить
}
Para não repetir o exemplo anterior, criaremos threads através de classes anônimas – ou seja, implementando imediatamente a interface Runnable.
/**
* Вот How добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

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

   public static void main(String args[]) {
       // один an object для двух тредов
       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();
   }
}

}
e saída para o console

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

Sincronização estática

Se você sincronizar um método estático, o bloqueio estará na classe, não no objeto. Neste exemplo, aplicamos a palavra-chave sincronizada a um método estático para realizar a sincronização estática:
/**
* Вот How добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
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();
   }
}
e a saída para o console:

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

47. O que é uma variável volátil?

A palavra-chave volatileé usada na programação multithread para fornecer segurança ao thread porque uma modificação em uma variável mutável é visível para todos os outros threads, portanto, uma variável pode ser usada por um thread por vez. Usando a palavra-chave, volatilevocê pode garantir que a variável será segura para threads e será armazenada na memória compartilhada, e os threads não a levarão para seu cache. Com o que se parece?
private volatile AtomicInteger count;
Apenas adicionamos à variável volatile. Mas isso não significa segurança total do thread... Afinal, as operações podem não ser atômicas em uma variável. Mas você pode usar Atomicclasses que realizam a operação de forma atômica, ou seja, em uma execução pelo processador. Muitas dessas classes podem ser encontradas no pacote java.util.concurrent.atomic.

48. O que é impasse

O impasse em Java faz parte do multithreading. Um deadlock pode ocorrer em uma situação em que um thread está aguardando um bloqueio de objeto adquirido por outro thread e um segundo thread está aguardando um bloqueio de objeto adquirido pelo primeiro thread. Assim, essas duas threads esperam uma pela outra e não continuarão executando seu código. As 50 principais perguntas e respostas da entrevista sobre Java Core.  Parte 3 - 5Vejamos um exemplo em que existe uma classe que implementa Runnable. Aceita dois recursos em seu construtor. Dentro do método run(), ele pega o bloqueio um por um, então se você criar dois objetos desta classe e transferir os recursos em ordens diferentes, você pode facilmente encontrar um bloqueio:
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);
           }
       }
   }
}
Saída do console:

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

49. Como evitar impasses?

Com base no que sabemos como ocorre um impasse, podemos tirar algumas conclusões...
  • Conforme mostrado no exemplo acima, o impasse ocorreu devido ao aninhamento de bloqueios. Ou seja, dentro de uma fechadura existe outra ou mais. Isso pode ser evitado da seguinte maneira - em vez de aninhar, você precisa adicionar uma nova abstração no topo e atribuir o bloqueio a um nível superior e remover os bloqueios aninhados.
  • Quanto mais bloqueio houver, maior será a chance de haver um impasse. Portanto, toda vez que você adiciona um cadeado, você precisa pensar se ele é realmente necessário e se adicionar um novo pode ser evitado.
  • Usos Thread.join(). Um deadlock também pode ocorrer quando um thread está esperando por outro. Para evitar esse problema, você pode considerar definir um limite de tempo para join()o método.
  • Se tivermos um thread, não haverá impasse ;)

50. O que é uma condição de corrida?

Se em corridas reais os carros têm desempenho, então na terminologia de corrida do multi-threading, os threads atuam nas corridas. Mas por que? Existem dois threads em execução e que podem ter acesso ao mesmo objeto. E eles podem tentar atualizar o estado ao mesmo tempo. Até agora está tudo claro, certo? Portanto, os threads funcionam em paralelo real (se houver mais de um núcleo no processador) ou em paralelo condicional, quando o processador aloca um curto período de tempo. E não podemos controlar esses processos, portanto não podemos garantir que quando um thread ler dados de um objeto, ele terá tempo de alterá-los ANTES que outro thread o faça. Problemas como esse acontecem quando essa combinação de testar e agir ocorre. O que isso significa? Por exemplo, temos ifuma expressão em cujo corpo a própria condição muda, ou seja:
int z = 0;

// проверь
if (z < 5) {
//действуй
   z = z + 5;
}
Portanto, pode haver uma situação em que dois threads entrem simultaneamente neste bloco de código em um momento em que z ainda é igual a zero e juntos alteram esse valor. E no final não obteremos o valor esperado de 5, mas sim de 10. Como evitar isso? Você precisa bloquear antes e depois da execução. Ou seja, para o primeiro thread entrar no bloco if, execute todas as ações, altere-o ze só então dê ao próximo thread a oportunidade de fazer isso. Mas a próxima thread não entrará no bloco if, pois zjá será igual a 5:
// получить блокировку для z
if (z < 5) {
   z = z + 5;
}
// выпустить из блокировки z
===================================================

Em vez de saída

Quero agradecer a todos que leram até o fim. Foi uma longa jornada e você conseguiu! Nem tudo pode estar claro. Isto é bom. Assim que comecei a aprender Java, não conseguia entender o que era uma variável estática. Mas nada, dormi com esse pensamento, li mais algumas fontes e finalmente entendi. Preparar-se para uma entrevista é mais uma questão acadêmica do que prática. Portanto, antes de cada entrevista, você precisa repetir e refrescar a memória de coisas que talvez não use com muita frequência.

E como sempre, links úteis:

Obrigado a todos pela leitura, até breve) Meu perfil no GitHub
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION