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...- Nós herdamos de
java.lang.Thread
- Implementamos uma interface cujo objeto aceita uma classe
java.lang.Runnable
construtoraThread
Herdamos da classe Thread
Para fazer isso funcionar, em nossa classe herdamos dejava.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 main
mé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 ojava.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?
Existem as seguintes diferenças entre um processo e um thread:- Um programa em execução é chamado de processo, enquanto Thread é um subconjunto de um processo.
- Os processos são independentes, enquanto os threads são um subconjunto de um processo.
- Os processos possuem espaços de endereçamento diferentes na memória, enquanto os threads contêm um espaço de endereçamento comum.
- A troca de contexto é mais rápida entre threads em comparação com processos.
- A comunicação entre processos é mais lenta e mais cara que a comunicação entre threads.
- 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?
- O multithreading permite que um aplicativo/programa sempre responda à entrada, mesmo que já esteja executando algumas tarefas em segundo plano;
- O multithreading permite concluir tarefas mais rapidamente porque os threads são executados de forma independente;
- O multithreading fornece melhor utilização do cache porque os threads compartilham recursos de memória comuns;
- 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?
- 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 ostart()
. - 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. - Em execução: neste estado, o agendador de threads seleciona um thread do estado pronto e ele é executado.
- Aguardando/Bloqueado: Neste estado, o thread não está em execução, mas ainda está ativo ou aguardando a conclusão de outro thread.
- 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, runtime
ele 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?
Thread 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 classejava.lang.Thread
fornece dois métodos para trabalhar com o daemon de thread:
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.public boolean isDaemon()
- essencialmente, este é um getter para a variáveldaemon
que definimos usando o método anterior.
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çãoIllegalThreadStateException
. 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 adicionarshutdown hook
usando 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-chavevolatile
é 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, volatile
você 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 Atomic
classes 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. Vejamos 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 parajoin()
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, temosif
uma 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 z
e 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 z
já 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:
- Exceções em Java
- Métodos padrão em java
- Análise detalhada da classe ArrayList
- Assine minha conta GitHub . Coloco tudo que faço lá. Tudo que eu ensino. Por exemplo, recentemente lidei com Drools RuleEngine .
- Exceções marcadas e não verificadas
- tente com recursos
- palavra-chave final
- Enquanto preparava o material, gostei muito do site https://www.geeksforgeeks.org . Há realmente muita informação legal lá.
GO TO FULL VERSION