CodeGym /Blogue Java /Random-PT /Melhor juntos: Java e a classe Thread. Parte II — Sincron...
John Squirrels
Nível 41
San Francisco

Melhor juntos: Java e a classe Thread. Parte II — Sincronização

Publicado no grupo Random-PT

Introdução

Então, sabemos que Java tem threads. Você pode ler sobre isso na revisão intitulada Better together: Java and the Thread class. Parte I — Threads de execução . Os threads são necessários para executar o trabalho em paralelo. Isso torna altamente provável que as threads interajam de alguma forma umas com as outras. Vejamos como isso acontece e quais ferramentas básicas temos. Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 1

Colheita

Thread.yield() é desconcertante e raramente usado. É descrito de muitas maneiras diferentes na Internet. Incluindo algumas pessoas escrevendo que existe alguma fila de threads, na qual um thread descerá com base nas prioridades do thread. Outras pessoas escrevem que um thread mudará seu status de "Running" para "Runnable" (mesmo que não haja distinção entre esses status, ou seja, Java não os distingue). A realidade é que tudo é muito menos conhecido e ainda mais simples em certo sentido. Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 2Há um bug ( JDK-6416721: (spec thread) Fix Thread.yield() javadoc ) registrado para a yield()documentação do método. Se você lê-lo, fica claro que oyield()na verdade, apenas fornece algumas recomendações ao agendador de encadeamento Java de que esse encadeamento pode receber menos tempo de execução. Mas o que realmente acontece, ou seja, se o escalonador age de acordo com a recomendação e o que ele faz em geral, depende da implementação da JVM e do sistema operacional. E pode depender de alguns outros fatores também. Toda a confusão provavelmente se deve ao fato de que o multithreading foi repensado à medida que a linguagem Java se desenvolveu. Leia mais na visão geral aqui: Breve introdução ao Java Thread.yield() .

Dormir

Um thread pode entrar em suspensão durante sua execução. Este é o tipo mais fácil de interação com outros segmentos. O sistema operacional que executa a máquina virtual Java que executa nosso código Java possui seu próprio agendador de encadeamentos . Ele decide qual thread iniciar e quando. Um programador não pode interagir com este escalonador diretamente do código Java, apenas através da JVM. Ele ou ela pode pedir ao escalonador para pausar o thread por um tempo, ou seja, colocá-lo para dormir. Você pode ler mais nestes artigos: Thread.sleep() e How Multithreading works . Você também pode verificar como os threads funcionam nos sistemas operacionais Windows: Internals of Windows Thread . E agora vamos ver com nossos próprios olhos. Salve o seguinte código em um arquivo chamado HelloWorldApp.java:

class HelloWorldApp {
    public static void main(String []args) {
        Runnable task = () -> {
            try {
                int secToWait = 1000 * 60;
                Thread.currentThread().sleep(secToWait);
                System.out.println("Woke up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(task);
        thread.start();
    }
}
Como você pode ver, temos uma tarefa que espera 60 segundos, após os quais o programa termina. Compilamos usando o comando " javac HelloWorldApp.java" e depois executamos o programa usando " java HelloWorldApp". É melhor iniciar o programa em uma janela separada. Por exemplo, no Windows, é assim: start java HelloWorldApp. Usamos o comando jps para obter o PID (ID do processo), e abrimos a lista de threads com " jvisualvm --openpid pid: Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 3Como você pode ver, nosso thread agora está com o status "Sleeping". Na verdade, existe uma maneira mais elegante de ajudar nosso segmento tem bons sonhos:

try {
	TimeUnit.SECONDS.sleep(60);
	System.out.println("Woke up");
} catch (InterruptedException e) {
	e.printStackTrace();
}
Você notou que estamos atendendo InterruptedExceptionem todos os lugares? Vamos entender o porquê.

Thread.interrupt()

O problema é que, enquanto um thread está esperando/dormindo, alguém pode querer interromper. Neste caso, lidamos com um arquivo InterruptedException. Este mecanismo foi criado depois que o Thread.stop()método foi declarado obsoleto, ou seja, desatualizado e indesejável. O motivo era que, quando o stop()método era chamado, o thread era simplesmente "morto", o que era muito imprevisível. Não podíamos saber quando o encadeamento seria interrompido e não podíamos garantir a consistência dos dados. Imagine que você está gravando dados em um arquivo enquanto o thread é encerrado. Em vez de matar o thread, os criadores do Java decidiram que seria mais lógico dizer a ele que deveria ser interrompido. Como responder a esta informação é uma questão para o próprio tópico decidir. Para obter mais detalhes, leia Por que Thread.stop foi preterido?no site da Oracle. Vejamos um exemplo:

public static void main(String []args) {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(60);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
Neste exemplo, não esperaremos 60 segundos. Em vez disso, exibiremos imediatamente "Interrompido". Isso ocorre porque chamamos o interrupt()método no thread. Este método define um sinalizador interno chamado "status de interrupção". Ou seja, cada thread possui um sinalizador interno que não é acessível diretamente. Mas temos métodos nativos para interagir com esse sinalizador. Mas essa não é a única maneira. Um thread pode estar em execução, não esperando por algo, simplesmente realizando ações. Mas pode antecipar que outros desejarão encerrar seu trabalho em um momento específico. Por exemplo:

public static void main(String []args) {
	Runnable task = () -> {
		while(!Thread.currentThread().isInterrupted()) {
			// Do some work
		}
		System.out.println("Finished");
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.interrupt();
}
No exemplo acima, o whileloop será executado até que a thread seja interrompida externamente. Quanto ao isInterruptedsinalizador, é importante saber que, se capturarmos um InterruptedException, o sinalizador isInterrupted será redefinido e isInterrupted()retornará falso. A classe Thread também tem um método estático Thread.interrupted() que se aplica apenas ao thread atual, mas esse método redefine o sinalizador para falso! Leia mais neste capítulo intitulado Interrupção de Thread .

Junte-se (Aguarde outro tópico terminar)

O tipo mais simples de espera é esperar que outro thread termine.

public static void main(String []args) throws InterruptedException {
	Runnable task = () -> {
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			System.out.println("Interrupted");
		}
	};
	Thread thread = new Thread(task);
	thread.start();
	thread.join();
	System.out.println("Finished");
}
Neste exemplo, o novo thread irá dormir 5 segundos. Ao mesmo tempo, o thread principal aguardará até que o thread adormecido acorde e termine seu trabalho. Se você observar o estado do encadeamento no JVisualVM, ele ficará assim: Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 4Graças às ferramentas de monitoramento, você pode ver o que está acontecendo com o encadeamento. O joinmétodo é bem simples, pois é apenas um método com código Java que executa wait()enquanto a thread na qual é chamada estiver ativa. Assim que o thread morre (quando termina seu trabalho), a espera é interrompida. E essa é toda a magia do join()método. Então, vamos passar para a coisa mais interessante.

Monitor

Multithreading inclui o conceito de um monitor. A palavra monitor vem do inglês por meio do latim do século XVI e significa "um instrumento ou dispositivo usado para observar, verificar ou manter um registro contínuo de um processo". No contexto deste artigo, tentaremos cobrir o básico. Para quem quiser os detalhes, mergulhe nos materiais vinculados. Começamos nossa jornada com a Java Language Specification (JLS): 17.1. Sincronização . Ele diz o seguinte: Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 5Acontece que Java usa um mecanismo de "monitoramento" para sincronização entre threads. Um monitor está associado a cada objeto e os threads podem adquiri-lo lock()ou liberá-lo com unlock(). A seguir, encontraremos o tutorial no site da Oracle: Intrinsic Locks and Synchronization. Este tutorial diz que a sincronização do Java é construída em torno de uma entidade interna chamada bloqueio intrínseco ou bloqueio de monitor . Esse bloqueio geralmente é chamado simplesmente de " monitor ". Também vemos novamente que todo objeto em Java tem um bloqueio intrínseco associado a ele. Você pode ler Java-Intrinsic Locks and Synchronization . A seguir será importante entender como um objeto em Java pode ser associado a um monitor. Em Java, cada objeto possui um cabeçalho que armazena metadados internos não disponíveis para o programador a partir do código, mas que a máquina virtual precisa para funcionar corretamente com os objetos. O cabeçalho do objeto inclui uma "palavra de marca", que se parece com isto: Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 6

https://edu.netbeans.org/contrib/slides/java-overview-and-java-se6.pdf

Aqui está um artigo do JavaWorld que é muito útil: Como a máquina virtual Java executa a sincronização de encadeamentos . Este artigo deve ser combinado com a descrição da seção "Resumo" do seguinte problema no sistema de rastreamento de erros do JDK: JDK-8183909 . Você pode ler a mesma coisa aqui: JEP-8183909 . Assim, em Java, um monitor é associado a um objeto e é usado para bloquear uma thread quando a thread tenta adquirir (ou obter) o bloqueio. Aqui está o exemplo mais simples:

public class HelloWorld{
    public static void main(String []args){
        Object object = new Object();
        synchronized(object) {
            System.out.println("Hello World");
        }
    }
}
Aqui, o thread atual (aquele no qual essas linhas de código são executadas) usa a synchronizedpalavra-chave para tentar usar o monitor associado aoobject"\variável para obter/adquirir o bloqueio. Se ninguém mais estiver disputando o monitor (ou seja, ninguém mais estiver executando código sincronizado usando o mesmo objeto), o Java poderá tentar executar uma otimização chamada "bloqueio tendencioso". Uma tag relevante e um registro sobre qual thread possui o bloqueio do monitor são adicionados à palavra de marca no cabeçalho do objeto. Isso reduz a sobrecarga necessária para bloquear um monitor. Se o monitor pertencia anteriormente a outro thread, esse bloqueio não é suficiente. A JVM alterna para o próximo tipo de bloqueio: "bloqueio básico". Ele usa operações de comparação e troca (CAS). Além do mais, a própria palavra de marca do cabeçalho do objeto não armazena mais a palavra de marca, mas sim uma referência ao local onde ela está armazenada, e a tag muda para que a JVM entenda que estamos usando bloqueio básico. Se vários threads competem (disputam) por um monitor (um adquiriu o bloqueio e um segundo está aguardando a liberação do bloqueio), a tag na palavra de marca muda e a palavra de marca agora armazena uma referência ao monitor como um objeto — alguma entidade interna da JVM. Conforme declarado na JDK Enchancement Proposal (JEP), esta situação requer espaço na área de memória Native Heap para armazenar esta entidade. A referência ao local de memória desta entidade interna será armazenada na palavra de marca do cabeçalho do objeto. Assim, um monitor é realmente um mecanismo para sincronizar o acesso a recursos compartilhados entre vários threads. A JVM alterna entre várias implementações desse mecanismo. Portanto, para simplificar, ao falar sobre o monitor, estamos falando de bloqueios. e um segundo está esperando que o bloqueio seja liberado), então a marca na palavra de marca muda e a palavra de marca agora armazena uma referência ao monitor como um objeto — alguma entidade interna da JVM. Conforme declarado na JDK Enchancement Proposal (JEP), esta situação requer espaço na área de memória Native Heap para armazenar esta entidade. A referência ao local de memória desta entidade interna será armazenada na palavra de marca do cabeçalho do objeto. Assim, um monitor é realmente um mecanismo para sincronizar o acesso a recursos compartilhados entre vários threads. A JVM alterna entre várias implementações desse mecanismo. Portanto, para simplificar, ao falar sobre o monitor, estamos falando de bloqueios. e um segundo está esperando que o bloqueio seja liberado), então a marca na palavra de marca muda e a palavra de marca agora armazena uma referência ao monitor como um objeto — alguma entidade interna da JVM. Conforme declarado na JDK Enchancement Proposal (JEP), esta situação requer espaço na área de memória Native Heap para armazenar esta entidade. A referência ao local de memória desta entidade interna será armazenada na palavra de marca do cabeçalho do objeto. Assim, um monitor é realmente um mecanismo para sincronizar o acesso a recursos compartilhados entre vários threads. A JVM alterna entre várias implementações desse mecanismo. Portanto, para simplificar, ao falar sobre o monitor, estamos falando de bloqueios. e a palavra de marca agora armazena uma referência ao monitor como um objeto — alguma entidade interna da JVM. Conforme declarado na JDK Enchancement Proposal (JEP), esta situação requer espaço na área de memória Native Heap para armazenar esta entidade. A referência ao local de memória desta entidade interna será armazenada na palavra de marca do cabeçalho do objeto. Assim, um monitor é realmente um mecanismo para sincronizar o acesso a recursos compartilhados entre vários threads. A JVM alterna entre várias implementações desse mecanismo. Portanto, para simplificar, ao falar sobre o monitor, estamos falando de bloqueios. e a palavra de marca agora armazena uma referência ao monitor como um objeto — alguma entidade interna da JVM. Conforme declarado na JDK Enchancement Proposal (JEP), esta situação requer espaço na área de memória Native Heap para armazenar esta entidade. A referência ao local de memória desta entidade interna será armazenada na palavra de marca do cabeçalho do objeto. Assim, um monitor é realmente um mecanismo para sincronizar o acesso a recursos compartilhados entre vários threads. A JVM alterna entre várias implementações desse mecanismo. Portanto, para simplificar, ao falar sobre o monitor, estamos falando de bloqueios. A referência ao local de memória desta entidade interna será armazenada na palavra de marca do cabeçalho do objeto. Assim, um monitor é realmente um mecanismo para sincronizar o acesso a recursos compartilhados entre vários threads. A JVM alterna entre várias implementações desse mecanismo. Portanto, para simplificar, ao falar sobre o monitor, estamos falando de bloqueios. A referência ao local de memória desta entidade interna será armazenada na palavra de marca do cabeçalho do objeto. Assim, um monitor é realmente um mecanismo para sincronizar o acesso a recursos compartilhados entre vários threads. A JVM alterna entre várias implementações desse mecanismo. Portanto, para simplificar, ao falar sobre o monitor, estamos falando de bloqueios. Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 7

Sincronizado (aguardando bloqueio)

Como vimos anteriormente, o conceito de "bloco sincronizado" (ou "seção crítica") está intimamente relacionado ao conceito de monitor. Dê uma olhada em um exemplo:

public static void main(String[] args) throws InterruptedException {
	Object lock = new Object();

	Runnable task = () -> {
		synchronized(lock) {
			System.out.println("thread");
		}
	};

	Thread th1 = new Thread(task);
	th1.start();
	synchronized(lock) {
		for (int i = 0; i < 8; i++) {
			Thread.currentThread().sleep(1000);
			System.out.print(" " + i);
		}
		System.out.println(" ...");
	}
}
Aqui, o thread principal primeiro passa o objeto de tarefa para o novo thread e, em seguida, adquire imediatamente o bloqueio e executa uma longa operação com ele (8 segundos). Todo esse tempo, a tarefa fica impossibilitada de prosseguir, pois não pode entrar no synchronizedbloco, pois o bloqueio já foi adquirido. Se o thread não conseguir obter o bloqueio, ele aguardará o monitor. Assim que obtiver o bloqueio, continuará a execução. Quando um thread sai de um monitor, ele libera o bloqueio. No JVisualVM, fica assim: Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 8Como você pode ver no JVisualVM, o status é "Monitor", o que significa que o thread está bloqueado e não pode assumir o monitor. Você também pode usar o código para determinar o status de um encadeamento, mas os nomes de status determinados dessa maneira não correspondem aos nomes usados ​​no JVisualVM, embora sejam semelhantes. Neste caso, oth1.getState()A instrução no loop for retornará BLOCKED , porque enquanto o loop estiver em execução, o lockmonitor do objeto será ocupado pelo mainthread e o th1thread será bloqueado e não poderá prosseguir até que o bloqueio seja liberado. Além dos blocos sincronizados, um método inteiro pode ser sincronizado. Por exemplo, aqui está um método da HashTableclasse:

public synchronized int size() {
	return count;
}
Este método será executado por apenas um thread em um determinado momento. Precisamos mesmo da fechadura? Sim, precisamos disso. No caso de métodos de instância, o objeto "this" (objeto atual) atua como um bloqueio. Há uma discussão interessante sobre este tópico aqui: Existe uma vantagem em usar um Método Sincronizado em vez de um Bloco Sincronizado? . Se o método for estático, o bloqueio não será o objeto "this" (porque não existe um objeto "this" para um método estático), mas sim um objeto Class (por exemplo, ) Integer.class.

Aguarde (esperando por um monitor). métodos notify() e notifyAll()

A classe Thread tem outro método de espera associado a um monitor. Ao contrário de sleep()e join(), esse método não pode ser simplesmente chamado. Seu nome é wait(). O waitmétodo é chamado no objeto associado ao monitor que queremos esperar. Vejamos um exemplo:

public static void main(String []args) throws InterruptedException {
	    Object lock = new Object();
	    // The task object will wait until it is notified via lock
	    Runnable task = () -> {
	        synchronized(lock) {
	            try {
	                lock.wait();
	            } catch(InterruptedException e) {
	                System.out.println("interrupted");
	            }
	        }
	        // After we are notified, we will wait until we can acquire the lock
	        System.out.println("thread");
	    };
	    Thread taskThread = new Thread(task);
	    taskThread.start();
        // We sleep. Then we acquire the lock, notify, and release the lock
	    Thread.currentThread().sleep(3000);
	    System.out.println("main");
	    synchronized(lock) {
	        lock.notify();
	    }
}
No JVisualVM, fica assim: Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 10Para entender como isso funciona, lembre-se de que os métodos wait()e notify()estão associados a java.lang.Object. Pode parecer estranho que métodos relacionados a threads estejam na Objectclasse. Mas a razão para isso agora se revela. Você deve se lembrar que todo objeto em Java tem um cabeçalho. O cabeçalho contém várias informações de manutenção, incluindo informações sobre o monitor, ou seja, o status da fechadura. Lembre-se, cada objeto, ou instância de uma classe, está associado a uma entidade interna na JVM, chamada de bloqueio intrínseco ou monitor. No exemplo acima, o código do objeto tarefa indica que entramos no bloco sincronizado para o monitor associado ao lockobjeto. Se conseguirmos adquirir o bloqueio para este monitor, entãowait()é chamado. A thread que executa a tarefa liberará o lockmonitor do objeto, mas entrará na fila de threads que aguardam notificação do lockmonitor do objeto. Essa fila de threads é chamada de WAIT SET, que reflete mais adequadamente seu propósito. Ou seja, é mais um conjunto do que uma fila. O mainthread cria um novo thread com o objeto de tarefa, inicia-o e aguarda 3 segundos. Isso torna altamente provável que o novo encadeamento consiga adquirir o bloqueio antes do mainencadeamento e entrar na fila do monitor. Depois disso, a mainprópria thread entra no lockbloco sincronizado do objeto e realiza a notificação da thread usando o monitor. Depois que a notificação é enviada, o mainthread libera olockmonitor do objeto, e a nova thread, que antes esperava a lockliberação do monitor do objeto, continua a execução. É possível enviar uma notificação para apenas um thread ( notify()) ou simultaneamente para todos os threads da fila ( notifyAll()). Leia mais aqui: Diferença entre notify() e notifyAll() em Java . É importante observar que a ordem de notificação depende de como a JVM é implementada. Leia mais aqui: Como resolver o starvation com notify e notifyAll? . A sincronização pode ser executada sem especificar um objeto. Você pode fazer isso quando um método inteiro é sincronizado em vez de um único bloco de código. Por exemplo, para métodos estáticos, o bloqueio será um objeto Class (obtido via .class):

public static synchronized void printA() {
	System.out.println("A");
}
public static void printB() {
	synchronized(HelloWorld.class) {
		System.out.println("B");
	}
}
Em termos de uso de bloqueios, ambos os métodos são iguais. Se um método não for estático, a sincronização será realizada usando o atual instance, ou seja, usando this. A propósito, dissemos anteriormente que você pode usar o getState()método para obter o status de um thread. Por exemplo, para um thread na fila esperando por um monitor, o status será WAITING ou TIMED_WAITING, se o wait()método especificar um tempo limite. Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 11

https://stackoverflow.com/questions/36425942/what-is-the-lifecycle-of-thread-in-java

Ciclo de vida do encadeamento

Ao longo de sua vida, o status de um thread muda. Na verdade, essas mudanças compõem o ciclo de vida do thread. Assim que um thread é criado, seu status é NOVO. Nesse estado, o novo encadeamento ainda não está em execução e o planejador de encadeamento Java ainda não sabe nada sobre ele. Para que o agendador de thread aprenda sobre o thread, você deve chamar o thread.start()método. Em seguida, o thread fará a transição para o estado RUNNABLE. A Internet tem muitos diagramas incorretos que distinguem entre os estados "Executável" e "Executando". Mas isso é um erro, porque o Java não distingue entre "pronto para funcionar" (executável) e "trabalhando" (em execução). Quando um thread está ativo, mas não ativo (não executável), ele está em um dos dois estados:
  • BLOQUEADO — aguardando para entrar em uma seção crítica, ou seja, um synchronizedbloco.
  • WAITING — esperando que outro thread satisfaça alguma condição.
Se a condição for satisfeita, o agendador de encadeamento iniciará o encadeamento. Se o encadeamento estiver esperando até um tempo especificado, seu status será TIMED_WAITING. Se o thread não estiver mais em execução (foi concluído ou uma exceção foi lançada), ele entrará no status TERMINATED. Para descobrir o estado de um thread, use o getState()método. Threads também possuem um isAlive()método, que retorna true se o thread não for TERMINADO.

LockSupport e thread parking

A partir do Java 1.6, surgiu um mecanismo interessante chamado LockSupport . Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 12Essa classe associa uma "permissão" a cada thread que a utiliza. Uma chamada para o park()método retorna imediatamente se a permissão estiver disponível, consumindo a permissão no processo. Caso contrário, bloqueia. Chamar o unparkmétodo torna a permissão disponível se ela ainda não estiver disponível. Existe apenas 1 autorização. A documentação do Java para LockSupportrefere-se à Semaphoreclasse. Vejamos um exemplo simples:

import java.util.concurrent.Semaphore;
public class HelloWorldApp{
    
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(0);
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            // Request the permit and wait until we get it
            e.printStackTrace();
        }
        System.out.println("Hello, World!");
    }
}
Este código sempre vai esperar, pois agora o semáforo tem 0 permissões. E quando acquire()é chamado no código (ou seja, solicita a permissão), a thread espera até receber a permissão. Como estamos esperando, devemos manipular InterruptedException. Curiosamente, o semáforo obtém um estado de thread separado. Se olharmos no JVisualVM, veremos que o estado não é "Wait", mas sim "Park". Melhor juntos: Java e a classe Thread.  Parte II — Sincronização - 13Vejamos outro exemplo:

public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            // Park the current thread
            System.err.println("Will be Parked");
            LockSupport.park();
            // As soon as we are unparked, we will start to act
            System.err.println("Unparked");
        };
        Thread th = new Thread(task);
        th.start();
        Thread.currentThread().sleep(2000);
        System.err.println("Thread state: " + th.getState());
        
        LockSupport.unpark(th);
        Thread.currentThread().sleep(2000);
}
O status da thread será WAITING, mas JVisualVM distingue entre waitda synchronizedpalavra-chave e parkda LockSupportclasse. Por que isso é LockSupporttão importante? Voltamos novamente para a documentação do Java e examinamos o estado do encadeamento WAITING . Como você pode ver, existem apenas três maneiras de entrar nele. Duas dessas maneiras são wait()e join(). E o terceiro é LockSupport. Em Java, os bloqueios também podem ser construídos em LockSupport e oferecer ferramentas de nível superior. Vamos tentar usar um. Por exemplo, dê uma olhada em ReentrantLock:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class HelloWorld{

    public static void main(String []args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Runnable task = () -> {
            lock.lock();
            System.out.println("Thread");
            lock.unlock();
        };
        lock.lock();

        Thread th = new Thread(task);
        th.start();
        System.out.println("main");
        Thread.currentThread().sleep(2000);
        lock.unlock();
    }
}
Assim como nos exemplos anteriores, tudo é simples aqui. O lockobjeto espera que alguém libere o recurso compartilhado. Se olharmos no JVisualVM, veremos que a nova thread ficará estacionada até que a mainthread libere o bloqueio para ela. Você pode ler mais sobre bloqueios aqui: Java 8 StampedLocks vs. ReadWriteLocks e Synchronized and Lock API in Java. Para entender melhor como os bloqueios são implementados, é útil ler sobre o Phaser neste artigo: Guide to the Java Phaser . E falando sobre vários sincronizadores, você deve ler o artigo DZone em The Java Synchronizers.

Conclusão

Nesta revisão, examinamos as principais maneiras pelas quais os encadeamentos interagem em Java. Material adicional: Melhor juntos: Java e a classe Thread. Parte I — Threads de execução Melhor juntos: Java e a classe Thread. Parte III — Interação melhor juntos: Java e a classe Thread. Parte IV — Callable, Future e amigos Melhor juntos: Java e a classe Thread. Parte V — Executor, ThreadPool, Fork/Join Better juntos: Java e a classe Thread. Parte VI — Atire!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION