Criação e controle
de threads
THREADSCONCORRÊNCIAE
PARALELISMOEMJAVA
Helder da Rocha (helder@summa.com.br)
1
1. Criação e controle de threads
2. Acesso exclusivo e comunicação entre threads
3. Ciclo de vida, aplicações e boas práticas
4. Variáveis atômicas
5. Travas
6. Coleções
7. Sincronizadores
8. Executores e Futures
9. Paralelismo
10. CompletableFuture
THREADSCONCORRÊNCIAE
PARALELISMOEMJAVA
Thread
• Uma sequência contínua de instruções em execução.
• É similar a um processo (do sistema operacional) mas com escopo
limitado a uma aplicação.
• Pode executar em paralelo com outros threads
• Pode ser interrompido quantas vezes for necessário, sempre continuando
do ponto onde parou.
+time
Single thread Thread-1 Thread-2 Core-1 Core-2
Single core Single core Dual core
Task 1
Task 2
Task 2
Task 1
Task 1
Task 2
Threadsexecutandotarefas
+time
Single thread Thread-1 Thread-2 Core-1 Core-2
Single core Single core Dual core
Task 1
Task 2
Task 2
Task 1
Task 1
Task 2
Threadsexecutandotarefas
Se há um único thread para executar duas tarefas, uma
aplicação precisa executá-las em sequência.A segunda
tarefa só é iniciada quando a primeira terminar.
+time
Single thread Thread-1 Thread-2 Core-1 Core-2
Single core Single core Dual core
Task 1
Task 2
Task 2
Task 1
Task 1
Task 2
Threadsexecutandotarefas
Duas tarefas rodando
concorrentemente
podem demorar mais
tempo para terminar,
mas garantem maior
responsividade
O sistema operacional
é responsável pela
mudança de contexto
e decide quanto
tempo cada thread
ocupa a CPU
Duas tarefas podem
executar ao mesmo
tempo, mesmo que
haja apenas um
processador.
+time
Single thread Thread-1 Thread-2 Core-1 Core-2
Single core Single core Dual core
Task 1
Task 2
Task 2
Task 1
Task 1
Task 2
Threadsexecutandotarefas
Com dois processadores ou dois cores, as duas
tarefas podem executar ao mesmo tempo, sem a
necessidade de mudança de contexto.
Ainda existe um overhead que pode atrasar o início
das aplicações, mas duas tarefas longas
provavelmente terminarão mais rapidamente
quando rodam em mais de um core.
ThreadsemJava
• Representados pela classe java.lang.Thread.
• Qualquer programa em Java possui pelo menos um thread, que executa
as instruções do método main().
• O thread principal é chamado de “main”.
OobjetoThread
• Pode-se obter uma referência ao objeto Thread do main() chamando
Thread.currentThread() dentro do main() ou qualquer método
chamado diretamente por main():
Thread principal = Thread.currentThread();
• Agora é possível chamar métodos de instância da classe Thread.
System.out.println("Nome do thread: " 

+ principal.getName()); // imprime main
System.out.println("Thread toString(): " 

+ principal); // imprime [main, 5, main]
AinterfaceRunnable
• A partir do thread main, podemos criar outros threads que irão rodar em
paralelo ou disputar a CPU com o thread principal.
• Todo thread precisa de uma sequência de instruções) para executar
• O thread main executa automaticamente o conteúdo do método static
void main(String[]) disparado pela JVM
• Threads adicionais executam automaticamente o conteúdo do método
void run() de uma classe que implementa java.lang.Runnable
ImplementandoRunnable
• A interface java.lang.Runnable é uma interface funcional
• Uma implementação de Runnable que imprime uma linha de texto e outra com
o nome do thread que está executando as instruções.
• Threads recebem nomes e IDs automaticamente. Nomes podem ser alterados.
public interface Runnable {
void run();
}
public class RunnableHelloWorld implements Runnable {
@Override public void run() {
System.out.println("Hello world paralelo!");
System.out.println("Eu sou o thread: "

+ Thread.currentThread().getName());
}
}
RodandoRunnablenomesmothread
• Um objeto Runnable é um objeto Java qualquer
• O programa abaixo executa o método run() no thread principal (e
imprime também o nome deste thread):
public class ThreadExampleSync {
public static void main(String[] args) {
Runnable paralelo = new RunnableHelloWorld();
paralelo.run();
System.out.println("Thread principal: " + 

Thread.currentThread().getName());
}
}
Hello world paralelo!
Eu sou o thread: main
Thread principal: main
Executando no thread main
ComoiniciarumnovoThread
• Para criar um novo Thread é preciso criar uma nova instância da classe
Thread, que recebe como argumento uma implementação de Runnable:
• Para iniciar (executar) o Thread e executar o conteúdo de run() através
desse novo Thread chame o método start() do Thread:
Runnable tarefa = new ImplementacaoDeRunnable();
Thread t = new Thread(tarefa);
t.start();
ExecutandoRunnableemnovoThread
• O programa abaixo cria um novo Thread com a tarefa Runnable e depois
o inicia com o método start():
public class ThreadExampleAsync {
public static void main(String[] args) {
Runnable paralelo = new RunnableHelloWorld();
Thread t1 = new Thread(paralelo);
t1.start();
System.out.println("Thread principal: " + 

Thread.currentThread().getName());
}
} Thread principal: main
Hello world paralelo!
Eu sou o thread: Thread-0Executando no thread Thread-0
main() {
Runnable task = new RunnableHelloWorld();
Thread t1 = new Thread(task);
t1.start();
System.out.print("Fim");
}
run() {
for(int i = 0; i < 10000; i++)
System.out.println(i);
}
Thread: main
Thread: Thread-0
main is done
Thread-0 is done
JVM is done
JVM
ExecutandoRunnableemnovoThread
Outrasformasdecriarthreads
• Estendendo a classe Thread
• Usando classes internas ou anônimas para implementar tarefas
• Usando expressões lambda para implementar tarefas
EstendendoaclasseThread
public class ThreadExampleAsync5 {
static class HelloThread extends Thread {
@Override public void run() {
System.out.println("Hello world from thread " 

+ this.getName());
}
}
public static void main(String[] args) {
new HelloThread().start();
System.out.println("Thread principal: " 

+ Thread.currentThread().getName());
}
}
Tarefacomoclasseinterna
public class ThreadExampleAsync2 {
public static void main(String[] args) {
class HelloParalelo implements Runnable {
@Override public void run() {
System.out.println("Hello world paralelo!");
}
}
Thread t1 = new Thread(new HelloParalelo());
t1.start();
System.out.println("Thread principal: " 

+ Thread.currentThread().getName());
}
}
Tarefacomoclasseanônima
public class ThreadExampleAsync3 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
System.out.println("Hello world paralelo!");
}
});
t1.start();
System.out.println("Thread principal: " 

+ Thread.currentThread().getName());
}
}
Tarefacomoexpressãolambda
public class ThreadExampleAsync4 {
public static void main(String[] args) {
Thread t1 = new Thread(
() -> System.out.println("Hello world paralelo!") );
t1.start();
System.out.println("Thread principal: " 

+ Thread.currentThread().getName());
}
}
Interrupçãodethreads
• Um thread só termina (normalmente) quando run() terminar.
• Métodos de interrupção não interrompem o thread. Ligam flag (INTERRUPT)
que deve ser usado para finalizá-lo (fazer run() terminar normalmente).
• Métodos de instância:
• void interrupt() – liga o flag INTERRUPT.
• boolean isInterrupted() – retorna true se INTERRUPT é true.
• Método estático (atua sobre Thread.currentThread()):
• static boolean interrupted() – retorna true se INTERRUPT for
true e em seguida muda INTERRUPT para false.
Threadqueterminacominterrupt()
public class InterruptRunnable implements Runnable {
@Override public void run() {
boolean interrupt = false;
while(!interrupt) {
interrupt = Thread.interrupted();
System.out.println(">INTERRUPT flag: " + interrupt);
}
System.out.println("INTERRUPTED flag: " + interrupt);
System.out.println("Thread " 

+ Thread.currentThread().getName() + " is DONE!");
}
}
Repete enquanto
interrupt for false
maininterrompendoThread-0
public class InterruptFlagExample {
public static void main(String[] args) {
Runnable runnable = new InterruptRunnable();
Thread t1 = new Thread(runnable);
t1.start();
// outras tarefas executadas pelo thread principal
t1.interrupt(); // liga o flag de interrupção em t1
System.out.println("Thread " 

+ Thread.currentThread().getName() + " is DONE!");
}
}
Pondothreadsparadormir
• O thread que está executando pode suspender sua execução por um
determinado tempo chamando Thread.sleep(milissegundos)
• Para dormir 1 segundo (1000 milissegundos):
• Thread.sleep(1000);
• Nesse intervalo, outros threads que estejam esperando acesso à CPU terão
oportunidade de executar, independente de sua prioridade.
• O método sleep() precisa lidar com InterruptedException
InterruptedException
• Será lançada se a flag INTERRUPT estiver ativada em um thread durante a
execução de sleep()
• Checked exception: precisa ser capturada ou declarada
• Isto pode ser usado para finalizar um thread, sem a necessidade de testar o
flag INTERRUPT
• Bloco try-catch pode capturar a exceção e finalizar o thread normalmente
• Bloco try-catch pode ignorar a exceção e a interrupção (a exceção
automaticamente muda o estado do INTERRUPT para false)
Finalizaçãocom
InterruptedException
public class RandomLetters implements Runnable {
@Override public void run() {
try {
while(true) {
System.out.print(" " + (char)('A' + new Random().nextInt(26)));
Thread.sleep(200);
}
} catch (InterruptedException e) {
System.out.println("n" + Thread.currentThread().getName() + " interrupted.");
System.out.println("INTERRUPTED flag: " + Thread.currentThread().isInterrupted());
}
System.out.println("Thread " + Thread.currentThread().getName() + " is DONE!");
}
}
public class InterruptSleepExample {
public static void main(String[] args) {
Thread t1 = new Thread(new RandomLetters());
t1.start();
// main thread executa suas tarefas
t1.interrupt(); // sets interrupt flag in t1
System.out.println("nThread " + 

Thread.currentThread().getName() + " is DONE!");
}
}
Religandooflagdeinterrupção
• Se o código decidir não interromper o thread, outra parte do programa
poderá assumir essa responsabilidade (é preciso religar o flag)
public void run() {
while(true) { // este loop continua mesmo com interrupção
try {
// chamadas externas que poderão lidar com o INTERRUPT
Thread.sleep(100);
} catch(InterruptedException e) {
System.out.println("Thread interrompido que não será finalizado."));
Thread.currentThread().interrupt(); // IMPORTANTE!
}
}
System.out.println("Thread finalizado."));
}
InterruptedException
desliga flag de interrupção
Liga novamente o flag
de interrupção
Esperandothreadsterminarem
• O método join() faz um thread esperar que o outro termine
• Se um thread t1 chama t2.join(), t1 é suspenso até que t2 termine
• Como todos os métodos que suspendem threads, join() lança
InterruptedException, que precisa ser capturada ou declarada.
Thread t1 = new Thread(() -> {
for(int i = 0; i < 10000; i++) System.out.println(i);
});
t1.start();
System.out.println("Waiting for " + t1.getName());
t1.join();
System.out.println("Thread main is DONE!");
main() {
Runnable task = new RunnableLoop();
Thread t1 = new Thread(task);
t1.start();
t1.join();
}
run() {
for(int i = 0; i < 10000; i++)
System.out.println(i);
}
Thread: main
Thread: Thread-1
main is done
Thread-1 is done
JVM is done
JVM
WAITING
for
Thread-1
Esperandothreadsterminarem
Daemonthreads
• Dois tipos de threads: user threads e daemon threads.
• Daemon threads existem apenas para servir aos user threads e são
destruídos quando não houver user threads executando
• User threads: o thread main e todos os threads criados até agora neste curso
• Daemon threads: garbage collector, ForkJoinPool
• Todo thread nasce user thread, exceto se for criado por um daemon thread,
ou se o método setDaemon(true) for chamado antes de começar a executar.
Daemonthreads
main() {
Runnable task = new RunnableHelloWorld();
Thread t1 = new Thread(task);
t1.setDaemon(true);
t1.start();
}
run() {
for(int i = 0; i < 10000; i++)
System.out.println(i);
}
Thread: main
Thread: Thread-1
main is doneJVM is done
JVM
Thread-1 interrupted
THREADSCONCORRÊNCIAE
PARALELISMOEMJAVA
Helder da Rocha (helder@summa.com.br)
github.com/helderdarocha/java8-course/
/java/concurrency/
Maio 2015

Threads 01: Criação e controle de threads

  • 1.
    Criação e controle dethreads THREADSCONCORRÊNCIAE PARALELISMOEMJAVA Helder da Rocha (helder@summa.com.br) 1
  • 2.
    1. Criação econtrole de threads 2. Acesso exclusivo e comunicação entre threads 3. Ciclo de vida, aplicações e boas práticas 4. Variáveis atômicas 5. Travas 6. Coleções 7. Sincronizadores 8. Executores e Futures 9. Paralelismo 10. CompletableFuture THREADSCONCORRÊNCIAE PARALELISMOEMJAVA
  • 3.
    Thread • Uma sequênciacontínua de instruções em execução. • É similar a um processo (do sistema operacional) mas com escopo limitado a uma aplicação. • Pode executar em paralelo com outros threads • Pode ser interrompido quantas vezes for necessário, sempre continuando do ponto onde parou.
  • 4.
    +time Single thread Thread-1Thread-2 Core-1 Core-2 Single core Single core Dual core Task 1 Task 2 Task 2 Task 1 Task 1 Task 2 Threadsexecutandotarefas
  • 5.
    +time Single thread Thread-1Thread-2 Core-1 Core-2 Single core Single core Dual core Task 1 Task 2 Task 2 Task 1 Task 1 Task 2 Threadsexecutandotarefas Se há um único thread para executar duas tarefas, uma aplicação precisa executá-las em sequência.A segunda tarefa só é iniciada quando a primeira terminar.
  • 6.
    +time Single thread Thread-1Thread-2 Core-1 Core-2 Single core Single core Dual core Task 1 Task 2 Task 2 Task 1 Task 1 Task 2 Threadsexecutandotarefas Duas tarefas rodando concorrentemente podem demorar mais tempo para terminar, mas garantem maior responsividade O sistema operacional é responsável pela mudança de contexto e decide quanto tempo cada thread ocupa a CPU Duas tarefas podem executar ao mesmo tempo, mesmo que haja apenas um processador.
  • 7.
    +time Single thread Thread-1Thread-2 Core-1 Core-2 Single core Single core Dual core Task 1 Task 2 Task 2 Task 1 Task 1 Task 2 Threadsexecutandotarefas Com dois processadores ou dois cores, as duas tarefas podem executar ao mesmo tempo, sem a necessidade de mudança de contexto. Ainda existe um overhead que pode atrasar o início das aplicações, mas duas tarefas longas provavelmente terminarão mais rapidamente quando rodam em mais de um core.
  • 8.
    ThreadsemJava • Representados pelaclasse java.lang.Thread. • Qualquer programa em Java possui pelo menos um thread, que executa as instruções do método main(). • O thread principal é chamado de “main”.
  • 9.
    OobjetoThread • Pode-se obteruma referência ao objeto Thread do main() chamando Thread.currentThread() dentro do main() ou qualquer método chamado diretamente por main(): Thread principal = Thread.currentThread(); • Agora é possível chamar métodos de instância da classe Thread. System.out.println("Nome do thread: " 
 + principal.getName()); // imprime main System.out.println("Thread toString(): " 
 + principal); // imprime [main, 5, main]
  • 10.
    AinterfaceRunnable • A partirdo thread main, podemos criar outros threads que irão rodar em paralelo ou disputar a CPU com o thread principal. • Todo thread precisa de uma sequência de instruções) para executar • O thread main executa automaticamente o conteúdo do método static void main(String[]) disparado pela JVM • Threads adicionais executam automaticamente o conteúdo do método void run() de uma classe que implementa java.lang.Runnable
  • 11.
    ImplementandoRunnable • A interfacejava.lang.Runnable é uma interface funcional • Uma implementação de Runnable que imprime uma linha de texto e outra com o nome do thread que está executando as instruções. • Threads recebem nomes e IDs automaticamente. Nomes podem ser alterados. public interface Runnable { void run(); } public class RunnableHelloWorld implements Runnable { @Override public void run() { System.out.println("Hello world paralelo!"); System.out.println("Eu sou o thread: "
 + Thread.currentThread().getName()); } }
  • 12.
    RodandoRunnablenomesmothread • Um objetoRunnable é um objeto Java qualquer • O programa abaixo executa o método run() no thread principal (e imprime também o nome deste thread): public class ThreadExampleSync { public static void main(String[] args) { Runnable paralelo = new RunnableHelloWorld(); paralelo.run(); System.out.println("Thread principal: " + 
 Thread.currentThread().getName()); } } Hello world paralelo! Eu sou o thread: main Thread principal: main Executando no thread main
  • 13.
    ComoiniciarumnovoThread • Para criarum novo Thread é preciso criar uma nova instância da classe Thread, que recebe como argumento uma implementação de Runnable: • Para iniciar (executar) o Thread e executar o conteúdo de run() através desse novo Thread chame o método start() do Thread: Runnable tarefa = new ImplementacaoDeRunnable(); Thread t = new Thread(tarefa); t.start();
  • 14.
    ExecutandoRunnableemnovoThread • O programaabaixo cria um novo Thread com a tarefa Runnable e depois o inicia com o método start(): public class ThreadExampleAsync { public static void main(String[] args) { Runnable paralelo = new RunnableHelloWorld(); Thread t1 = new Thread(paralelo); t1.start(); System.out.println("Thread principal: " + 
 Thread.currentThread().getName()); } } Thread principal: main Hello world paralelo! Eu sou o thread: Thread-0Executando no thread Thread-0
  • 15.
    main() { Runnable task= new RunnableHelloWorld(); Thread t1 = new Thread(task); t1.start(); System.out.print("Fim"); } run() { for(int i = 0; i < 10000; i++) System.out.println(i); } Thread: main Thread: Thread-0 main is done Thread-0 is done JVM is done JVM ExecutandoRunnableemnovoThread
  • 16.
    Outrasformasdecriarthreads • Estendendo aclasse Thread • Usando classes internas ou anônimas para implementar tarefas • Usando expressões lambda para implementar tarefas
  • 17.
    EstendendoaclasseThread public class ThreadExampleAsync5{ static class HelloThread extends Thread { @Override public void run() { System.out.println("Hello world from thread " 
 + this.getName()); } } public static void main(String[] args) { new HelloThread().start(); System.out.println("Thread principal: " 
 + Thread.currentThread().getName()); } }
  • 18.
    Tarefacomoclasseinterna public class ThreadExampleAsync2{ public static void main(String[] args) { class HelloParalelo implements Runnable { @Override public void run() { System.out.println("Hello world paralelo!"); } } Thread t1 = new Thread(new HelloParalelo()); t1.start(); System.out.println("Thread principal: " 
 + Thread.currentThread().getName()); } }
  • 19.
    Tarefacomoclasseanônima public class ThreadExampleAsync3{ public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println("Hello world paralelo!"); } }); t1.start(); System.out.println("Thread principal: " 
 + Thread.currentThread().getName()); } }
  • 20.
    Tarefacomoexpressãolambda public class ThreadExampleAsync4{ public static void main(String[] args) { Thread t1 = new Thread( () -> System.out.println("Hello world paralelo!") ); t1.start(); System.out.println("Thread principal: " 
 + Thread.currentThread().getName()); } }
  • 21.
    Interrupçãodethreads • Um threadsó termina (normalmente) quando run() terminar. • Métodos de interrupção não interrompem o thread. Ligam flag (INTERRUPT) que deve ser usado para finalizá-lo (fazer run() terminar normalmente). • Métodos de instância: • void interrupt() – liga o flag INTERRUPT. • boolean isInterrupted() – retorna true se INTERRUPT é true. • Método estático (atua sobre Thread.currentThread()): • static boolean interrupted() – retorna true se INTERRUPT for true e em seguida muda INTERRUPT para false.
  • 22.
    Threadqueterminacominterrupt() public class InterruptRunnableimplements Runnable { @Override public void run() { boolean interrupt = false; while(!interrupt) { interrupt = Thread.interrupted(); System.out.println(">INTERRUPT flag: " + interrupt); } System.out.println("INTERRUPTED flag: " + interrupt); System.out.println("Thread " 
 + Thread.currentThread().getName() + " is DONE!"); } } Repete enquanto interrupt for false
  • 23.
    maininterrompendoThread-0 public class InterruptFlagExample{ public static void main(String[] args) { Runnable runnable = new InterruptRunnable(); Thread t1 = new Thread(runnable); t1.start(); // outras tarefas executadas pelo thread principal t1.interrupt(); // liga o flag de interrupção em t1 System.out.println("Thread " 
 + Thread.currentThread().getName() + " is DONE!"); } }
  • 24.
    Pondothreadsparadormir • O threadque está executando pode suspender sua execução por um determinado tempo chamando Thread.sleep(milissegundos) • Para dormir 1 segundo (1000 milissegundos): • Thread.sleep(1000); • Nesse intervalo, outros threads que estejam esperando acesso à CPU terão oportunidade de executar, independente de sua prioridade. • O método sleep() precisa lidar com InterruptedException
  • 25.
    InterruptedException • Será lançadase a flag INTERRUPT estiver ativada em um thread durante a execução de sleep() • Checked exception: precisa ser capturada ou declarada • Isto pode ser usado para finalizar um thread, sem a necessidade de testar o flag INTERRUPT • Bloco try-catch pode capturar a exceção e finalizar o thread normalmente • Bloco try-catch pode ignorar a exceção e a interrupção (a exceção automaticamente muda o estado do INTERRUPT para false)
  • 26.
    Finalizaçãocom InterruptedException public class RandomLettersimplements Runnable { @Override public void run() { try { while(true) { System.out.print(" " + (char)('A' + new Random().nextInt(26))); Thread.sleep(200); } } catch (InterruptedException e) { System.out.println("n" + Thread.currentThread().getName() + " interrupted."); System.out.println("INTERRUPTED flag: " + Thread.currentThread().isInterrupted()); } System.out.println("Thread " + Thread.currentThread().getName() + " is DONE!"); } } public class InterruptSleepExample { public static void main(String[] args) { Thread t1 = new Thread(new RandomLetters()); t1.start(); // main thread executa suas tarefas t1.interrupt(); // sets interrupt flag in t1 System.out.println("nThread " + 
 Thread.currentThread().getName() + " is DONE!"); } }
  • 27.
    Religandooflagdeinterrupção • Se ocódigo decidir não interromper o thread, outra parte do programa poderá assumir essa responsabilidade (é preciso religar o flag) public void run() { while(true) { // este loop continua mesmo com interrupção try { // chamadas externas que poderão lidar com o INTERRUPT Thread.sleep(100); } catch(InterruptedException e) { System.out.println("Thread interrompido que não será finalizado.")); Thread.currentThread().interrupt(); // IMPORTANTE! } } System.out.println("Thread finalizado.")); } InterruptedException desliga flag de interrupção Liga novamente o flag de interrupção
  • 28.
    Esperandothreadsterminarem • O métodojoin() faz um thread esperar que o outro termine • Se um thread t1 chama t2.join(), t1 é suspenso até que t2 termine • Como todos os métodos que suspendem threads, join() lança InterruptedException, que precisa ser capturada ou declarada. Thread t1 = new Thread(() -> { for(int i = 0; i < 10000; i++) System.out.println(i); }); t1.start(); System.out.println("Waiting for " + t1.getName()); t1.join(); System.out.println("Thread main is DONE!");
  • 29.
    main() { Runnable task= new RunnableLoop(); Thread t1 = new Thread(task); t1.start(); t1.join(); } run() { for(int i = 0; i < 10000; i++) System.out.println(i); } Thread: main Thread: Thread-1 main is done Thread-1 is done JVM is done JVM WAITING for Thread-1 Esperandothreadsterminarem
  • 30.
    Daemonthreads • Dois tiposde threads: user threads e daemon threads. • Daemon threads existem apenas para servir aos user threads e são destruídos quando não houver user threads executando • User threads: o thread main e todos os threads criados até agora neste curso • Daemon threads: garbage collector, ForkJoinPool • Todo thread nasce user thread, exceto se for criado por um daemon thread, ou se o método setDaemon(true) for chamado antes de começar a executar.
  • 31.
    Daemonthreads main() { Runnable task= new RunnableHelloWorld(); Thread t1 = new Thread(task); t1.setDaemon(true); t1.start(); } run() { for(int i = 0; i < 10000; i++) System.out.println(i); } Thread: main Thread: Thread-1 main is doneJVM is done JVM Thread-1 interrupted
  • 32.
    THREADSCONCORRÊNCIAE PARALELISMOEMJAVA Helder da Rocha(helder@summa.com.br) github.com/helderdarocha/java8-course/ /java/concurrency/ Maio 2015