JavaRush /Blogue Java /Random-PT /Você não pode estragar o Java com um thread: Parte I - Th...
Viacheslav
Nível 3

Você não pode estragar o Java com um thread: Parte I - Threads

Publicado no grupo Random-PT

Introdução

O multithreading foi incorporado ao Java desde seus primeiros dias. Então, vamos dar uma olhada rápida no que é multithreading. Você não pode estragar o Java com um Thread: Parte I - Threads - 1Vamos tomar como ponto de partida a lição oficial da Oracle: " Lição: A aplicação "Hello World!" ". Vamos mudar um pouco o código da nossa aplicação Hello World para o seguinte:
class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello, " + args[0]);
    }
}
argsé uma matriz de parâmetros de entrada passados ​​quando o programa é iniciado. Vamos salvar esse código em um arquivo com um nome que corresponda ao nome da classe e à extensão .java. Vamos compilar usando o utilitário javac : javac HelloWorldApp.java Depois disso, chame nosso código com algum parâmetro, por exemplo, Roger: java HelloWorldApp Roger Você não pode estragar o Java com um Thread: Parte I - Threads - 2Nosso código agora tem uma falha séria. Se não passarmos nenhum argumento (ou seja, apenas executar java HelloWorldApp), obteremos um erro:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
        at HelloWorldApp.main(HelloWorldApp.java:3)
Ocorreu uma exceção (ou seja, um erro) em um thread chamado main. Acontece que existe algum tipo de thread em Java? É aqui que nossa jornada começa.

Java e threads

Para entender o que é um thread, você precisa entender como um aplicativo Java é iniciado. Vamos alterar nosso código da seguinte forma:
class HelloWorldApp {
    public static void main(String[] args) {
		while (true) {
			//Do nothing
		}
	}
}
Agora vamos compilá-lo novamente usando javac. A seguir, por conveniência, executaremos nosso código Java em uma janela separada. No Windows você pode fazer assim: start java HelloWorldApp. Agora, usando o utilitário jps , vamos ver quais informações o Java nos dirá: Você não pode estragar o Java com um Thread: Parte I - Threads - 3O primeiro número é o PID ou Process ID, o identificador do processo. O que é um processo?
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
Com a ajuda de processos, a execução de diferentes programas é isolada umas das outras: cada aplicação utiliza sua própria área de memória sem interferir em outros programas. Aconselho você a ler o artigo com mais detalhes: " https://habr.com/post/164487/ ". Um processo não pode existir sem threads, portanto, se existe um processo, pelo menos um thread existe nele. Como isso acontece em Java? Quando executamos um programa Java, sua execução começa com a extensão main. Nós meio que entramos no programa, então esse método especial mainé chamado de ponto de entrada, ou “ponto de entrada”. O método maindeve ser sempre public static voidtal que a Java Virtual Machine (JVM) possa começar a executar nosso programa. Consulte " Por que o método principal Java é estático? " para obter mais detalhes. Acontece que o launcher java (java.exe ou javaw.exe) é um aplicativo simples (aplicativo C simples): ele carrega diversas DLLs, que na verdade são a JVM. O iniciador Java faz um conjunto específico de chamadas Java Native Interface (JNI). JNI é o mecanismo que une o mundo da Java Virtual Machine e o mundo do C++. Acontece que o launcher não é a JVM, mas sim o seu carregador. Ele conhece os comandos corretos a serem executados para iniciar a JVM. Sabe organizar todo o ambiente necessário utilizando chamadas JNI. Essa organização do ambiente também inclui a criação de uma thread principal, que normalmente é chamada de main. Para ver mais claramente quais threads residem em um processo java, usamos o programa jvisualvm , que está incluído no JDK. Conhecendo o pid de um processo, podemos abrir dados sobre ele imediatamente: jvisualvm --openpid айдипроцесса Você não pode estragar o Java com um Thread: Parte I - Threads - 4Curiosamente, cada thread tem sua própria área separada na memória alocada para o processo. Essa estrutura de memória é chamada de pilha. Uma pilha consiste em quadros. Um quadro é o ponto de chamada de um método, ponto de execução. Um quadro também pode ser representado como StackTraceElement (consulte API Java para StackTraceElement ). Você pode ler mais sobre a memória alocada para cada thread aqui . Se olharmos a API Java e procurarmos a palavra Thread, veremos que existe uma classe java.lang.Thread . É essa classe que representa um stream em Java, e é com ela que temos que trabalhar. Thread'ом Java не испортишь: Часть I — потоки - 5

java.lang.Thread

Um thread em Java é representado como uma instância da classe java.lang.Thread. Vale a pena entender imediatamente que as instâncias da classe Thread em Java não são threads em si. Este é apenas um tipo de API para threads de baixo nível gerenciados pela JVM e pelo sistema operacional. Quando iniciamos a JVM usando o iniciador Java, ela cria um thread principal com um nome maine vários outros threads de serviço. Conforme declarado no JavaDoc da classe Thread: When a Java Virtual Machine starts up, there is usually a single non-daemon thread Existem 2 tipos de threads: daemons e não-daemons. Threads daemon são threads de segundo plano (threads de serviço) que realizam algum trabalho em segundo plano. Este termo interessante é uma referência ao “demônio de Maxwell”, sobre o qual você pode ler mais no artigo da Wikipedia sobre “ demônios ”. Conforme indicado na documentação, a JVM continua executando o programa (processo) até:
  • O método Runtime.exit não é chamado
  • Todos os threads não-daemon concluíram seu trabalho (sem erros e com exceções lançadas)
Daí o detalhe importante: threads daemon podem ser encerrados em qualquer comando sendo executado. Portanto, a integridade dos dados neles contidos não é garantida. Portanto, os threads daemon são adequados para algumas tarefas de serviço. Por exemplo, em Java existe um thread que é responsável por processar métodos de finalização ou threads relacionados ao Garbage Collector (GC). Cada thread pertence a algum grupo ( ThreadGroup ). E os grupos podem entrar uns nos outros, formando alguma hierarquia ou estrutura.
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());
}
Os grupos permitem agilizar o gerenciamento dos fluxos e acompanhá-los. Além dos grupos, os threads possuem seu próprio manipulador de exceções. Vejamos um exemplo:
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);
}
A divisão por zero causará um erro que será detectado pelo manipulador. Se você não especificar o manipulador, a implementação do manipulador padrão funcionará, exibindo a pilha de erros em StdError. Você pode ler mais na revisão http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ ". Além disso, o tópico tem uma prioridade. Você pode ler mais sobre prioridades no artigo " Prioridade de thread Java em multithreading ".

Criando um tópico

Conforme declarado na documentação, temos 2 maneiras de criar um thread. A primeira é criar seu próprio herdeiro. Por exemplo:
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 você pode ver, a tarefa é iniciada no método rune o thread é iniciado no método start. Eles não devem ser confundidos, porque... se executarmos o método rundiretamente, nenhum novo thread será iniciado. É o método startque pede à JVM para criar um novo thread. A opção com descendente de Thread é ruim porque incluímos Thread na hierarquia de classes. A segunda desvantagem é que estamos começando a violar o princípio da “Responsabilidade Exclusiva” SOLID, porque nossa classe se torna simultaneamente responsável por gerenciar o thread e por alguma tarefa que deve ser executada neste thread. Qual é correto? A resposta está no próprio método runque substituímos:
public void run() {
	if (target != null) {
		target.run();
	}
}
Aqui targetestão alguns java.lang.Runnableque podemos passar para Thread ao criar uma instância da classe. Portanto, podemos fazer isso:
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();
    }
}
Também é Runnableuma interface funcional desde Java 1.8. Isso permite que você escreva código de tarefa para threads de maneira ainda mais bonita:
public static void main(String []args){
	Runnable task = () -> {
		System.out.println("Hello, World!");
	};
	Thread thread = new Thread(task);
	thread.start();
}

Total

Então, espero que com esta história fique claro o que é um stream, como eles existem e quais operações básicas podem ser realizadas com eles. Na próxima parte , vale entender como as threads interagem entre si e qual é o seu ciclo de vida. #Viacheslav
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION