Quando devo usar uma ThreadLocal
variável?
Como isso é usado?
RequestUtils.getCurrentRequestThreadLocal()
. Não dizendo que isso é muito elegante, mas isso decorre do fato de o ThreadLocal em si não ser muito elegante na maioria dos casos.
Quando devo usar uma ThreadLocal
variável?
Como isso é usado?
RequestUtils.getCurrentRequestThreadLocal()
. Não dizendo que isso é muito elegante, mas isso decorre do fato de o ThreadLocal em si não ser muito elegante na maioria dos casos.
Respostas:
Um uso possível (e comum) é quando você tem algum objeto que não é seguro para threads, mas deseja evitar sincronizar o acesso a esse objeto (estou olhando para você, SimpleDateFormat ). Em vez disso, dê a cada thread sua própria instância do objeto.
Por exemplo:
public class Foo
{
// SimpleDateFormat is not thread-safe, so give one to each thread
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
public String formatIt(Date date)
{
return formatter.get().format(date);
}
}
SimpleDateFormat
. Talvez seja melhor usar uma alternativa segura para threads . Se você concorda que singletons são ruins , ThreadLocal
é ainda pior.
Como a ThreadLocal
é uma referência aos dados em um determinado Thread
, você pode acabar com vazamentos de carregamento de classe ao usar ThreadLocal
s em servidores de aplicativos usando conjuntos de encadeamentos. Você precisa ter muito cuidado com a limpeza de quaisquer ThreadLocal
s você get()
ou set()
usando o ThreadLocal
's remove()
método.
Se você não fizer a limpeza quando terminar, quaisquer referências que ele contenha para classes carregadas como parte de um webapp implantado permanecerão no heap permanente e nunca serão coletadas como lixo. A reimplantação / desimplantação do aplicativo da web não limpará a Thread
referência de cada uma das classes do aplicativo da web, uma vez que Thread
não é algo de propriedade do seu aplicativo da web. Cada implantação sucessiva criará uma nova instância da classe que nunca será coletada como lixo.
Você terminará com exceções de falta de memória devido a java.lang.OutOfMemoryError: PermGen space
e após alguns estudos no Google provavelmente aumentará em -XX:MaxPermSize
vez de corrigir o erro.
Se você enfrentar esses problemas, poderá determinar qual thread e classe está mantendo essas referências usando o Memory Analyzer do Eclipse e / ou seguindo o guia e acompanhamento de Frank Kieviet .
Atualização: redescobri a entrada do blog de Alex Vasseur que me ajudou a rastrear alguns ThreadLocal
problemas que eu estava tendo.
Muitas estruturas usam ThreadLocals para manter algum contexto relacionado ao segmento atual. Por exemplo, quando a transação atual é armazenada em um ThreadLocal, você não precisa transmiti-la como parâmetro em todas as chamadas de métodos, caso alguém da pilha precise acessar a mesma. Os aplicativos da Web podem armazenar informações sobre a solicitação e sessão atuais em um ThreadLocal, para que o aplicativo tenha acesso fácil a eles. Com o Guice, você pode usar o ThreadLocals ao implementar escopos personalizados para os objetos injetados (o padrão do Guice escopos de servlet provavelmente os usam também).
ThreadLocals são um tipo de variáveis globais (embora um pouco menos ruins porque estão restritas a um thread), portanto, você deve ter cuidado ao usá-las para evitar efeitos colaterais indesejados e vazamentos de memória. Crie suas APIs para que os valores ThreadLocal sempre sejam limpos automaticamente quando não forem mais necessários e que o uso incorreto da API não seja possível (por exemplo, assim ). O ThreadLocals pode ser usado para tornar o código mais limpo e, em alguns casos raros, é a única maneira de fazer alguma coisa funcionar (meu projeto atual teve dois desses casos; eles estão documentados aqui em "Campos estáticos e variáveis globais").
Em Java, se você possui um dado que pode variar por segmento, suas opções são passá-lo a todos os métodos que precisam (ou podem precisar) dele ou associar o dado ao segmento. Passar o dado por todo o lado pode ser viável se todos os seus métodos já precisarem passar por uma variável "contexto" comum.
Se não for esse o caso, convém não desorganizar as assinaturas do método com um parâmetro adicional. Em um mundo não encadeado, você pode resolver o problema com o equivalente em Java de uma variável global. Em uma palavra encadeada, o equivalente a uma variável global é uma variável local de encadeamento.
'datum' is the singular form and 'data' is the plural form.
Há um exemplo muito bom no livro Java Concurrency in Practice . Onde o autor ( Joshua Bloch ) explica como o confinamento do Thread é uma das maneiras mais simples de obter segurança de thread e o ThreadLocal é o meio mais formal de manter o confinamento do thread. No final, ele também explica como as pessoas podem abusar dele usando-o como variáveis globais.
Copiei o texto do livro mencionado, mas o código 3.10 está ausente, pois não é muito importante entender onde o ThreadLocal deve ser usado.
As variáveis locais de encadeamento são frequentemente usadas para impedir o compartilhamento em projetos baseados em Singletons mutáveis ou variáveis globais. Por exemplo, um aplicativo de thread único pode manter uma conexão de banco de dados global inicializada na inicialização para evitar a necessidade de passar uma Conexão para todos os métodos. Como as conexões JDBC podem não ser seguras para threads, um aplicativo multithread que usa uma conexão global sem coordenação adicional também não é seguro para threads. Usando um ThreadLocal para armazenar a conexão JDBC, como no ConnectionHolder na Listagem 3.10, cada encadeamento terá sua própria conexão.
O ThreadLocal é amplamente usado na implementação de estruturas de aplicativos. Por exemplo, os contêineres J2EE associam um contexto de transação a um encadeamento em execução pela duração de uma chamada EJB. Isso é facilmente implementado usando um Thread-Local estático que mantém o contexto da transação: quando o código da estrutura precisa determinar qual transação está em execução no momento, ele busca o contexto da transação neste ThreadLocal. Isso é conveniente, pois reduz a necessidade de passar informações de contexto de execução para todos os métodos, mas associa qualquer código que use esse mecanismo à estrutura.
É fácil abusar do ThreadLocal, tratando sua propriedade de confinamento de threads como uma licença para usar variáveis globais ou como um meio de criar argumentos de método "ocultos". Como as variáveis globais, as variáveis locais do encadeamento podem prejudicar a reutilização e introduzir acoplamentos ocultos entre as classes e, portanto, devem ser usadas com cuidado.
Essencialmente, quando você precisa que o valor de uma variável dependa do encadeamento atual e não seja conveniente anexar o valor ao encadeamento de alguma outra maneira (por exemplo, encadeamento de subclassificação).
Um caso típico é onde alguma outra estrutura criou o encadeamento em que seu código está sendo executado, por exemplo, um contêiner de servlet ou onde faz mais sentido usar ThreadLocal porque sua variável está "em seu lugar lógico" (em vez de uma variável pendurado em uma subclasse Thread ou em outro mapa de hash).
No meu site, tenho mais algumas discussões e exemplos de quando usar o ThreadLocal que também podem ser interessantes.
Algumas pessoas defendem o uso do ThreadLocal como uma maneira de anexar um "ID do thread" a cada thread em certos algoritmos simultâneos em que você precisa de um número de thread (consulte, por exemplo, Herlihy & Shavit). Nesses casos, verifique se você está realmente recebendo um benefício!
O ThreadLocal em Java foi introduzido no JDK 1.2, mas mais tarde foi gerado no JDK 1.5 para introduzir segurança de tipo na variável ThreadLocal.
O ThreadLocal pode ser associado ao escopo do Thread, todo o código executado pelo Thread tem acesso às variáveis ThreadLocal, mas dois threads não podem ver a variável ThreadLocal um do outro.
Cada encadeamento contém uma cópia exclusiva da variável ThreadLocal que se torna elegível para a coleta de lixo após o encadeamento terminar ou morrer, normalmente ou devido a qualquer exceção, considerando que a variável ThreadLocal não possui outras referências ativas.
As variáveis ThreadLocal em Java geralmente são campos estáticos privados em Classes e mantêm seu estado dentro do Thread.
Leia mais: ThreadLocal em Java - programa e tutorial de exemplo
A documentação diz muito bem: "cada thread que acessa [uma variável local de thread] (por meio de seu método get ou set) possui sua própria cópia inicializada da variável, independente".
Você usa um quando cada segmento deve ter sua própria cópia de algo. Por padrão, os dados são compartilhados entre os threads.
Dois casos de uso em que a variável threadlocal pode ser usada -
1- Quando temos um requisito para associar o estado a um encadeamento (por exemplo, um ID do usuário ou Transaction). Isso geralmente acontece com um aplicativo da Web que toda solicitação que vai para um servlet tem um transactionID exclusivo associado a ele.
// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
Observe que aqui o método withInitial é implementado usando a expressão lambda.
2- Outro caso de uso é quando queremos ter uma instância segura de thread e não queremos usar a sincronização, pois o custo de desempenho com a sincronização é maior. Um desses casos é quando SimpleDateFormat é usado. Como o SimpleDateFormat não é seguro para threads, precisamos fornecer um mecanismo para torná-lo seguro.
public class ThreadLocalDemo1 implements Runnable {
// threadlocal variable is created
private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue(){
System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
return new SimpleDateFormat("dd/MM/yyyy");
}
};
public static void main(String[] args) {
ThreadLocalDemo1 td = new ThreadLocalDemo1();
// Two threads are created
Thread t1 = new Thread(td, "Thread-1");
Thread t2 = new Thread(td, "Thread-2");
t1.start();
t2.start();
}
@Override
public void run() {
System.out.println("Thread run execution started for " + Thread.currentThread().getName());
System.out.println("Date formatter pattern is " + dateFormat.get().toPattern());
System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
}
}
Desde o lançamento do Java 8, há uma maneira mais declarativa de inicializar ThreadLocal
:
ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");
Até o lançamento do Java 8, você precisava fazer o seguinte:
ThreadLocal<String> local = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "init value";
}
};
Além disso, se o método de instanciação (construtor, método de fábrica) da classe usada para ThreadLocal
não aceitar nenhum parâmetro, você poderá simplesmente usar referências de método (introduzidas no Java 8):
class NotThreadSafe {
// no parameters
public NotThreadSafe(){}
}
ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);
Nota: A
avaliação é lenta, pois você está passando o java.util.function.Supplier
lambda que é avaliado apenas quando ThreadLocal#get
é chamado, mas o valor não foi avaliado anteriormente.
Você precisa ter muito cuidado com o padrão ThreadLocal. Existem algumas desvantagens importantes, como Phil mencionou, mas uma que não foi mencionada é garantir que o código que configura o contexto ThreadLocal não seja "reentrante".
Coisas ruins podem acontecer quando o código que define as informações é executado uma segunda ou terceira vez, porque as informações no seu encadeamento podem começar a sofrer alterações quando você não esperava. Portanto, verifique se as informações do ThreadLocal não foram definidas antes de defini-las novamente.
ThreadLocal
padrão. Se você fizer F(){ member=random(); F2(); write(member); }
e F2 substituir o membro por um novo valor, obviamente write(member)
não gravará mais o número que você random()
editou. Isso é literalmente apenas senso comum. Da mesma forma, se o fizer F(){ F(); }
, então dê sorte com seu loop infinito! Isso é verdade em todos os lugares e não é específico ThreadLocal
.
quando?
Quando um objeto não é seguro para threads, em vez da sincronização que dificulta a escalabilidade, atribua um objeto a cada thread e mantenha-o no escopo do thread, que é ThreadLocal. Um dos objetos usados com mais freqüência, mas não com segurança de encadeamento, é o banco de dados Connection e JMSConnection.
Como ?
Um exemplo é o framework Spring que usa o ThreadLocal fortemente para gerenciar transações nos bastidores, mantendo esses objetos de conexão nas variáveis ThreadLocal. Em um nível alto, quando uma transação é iniciada, ela obtém a conexão (e desativa a confirmação automática) e a mantém em ThreadLocal. em chamadas db adicionais, ele usa a mesma conexão para se comunicar com db. No final, ele pega a conexão do ThreadLocal e confirma (ou reverte) a transação e libera a conexão.
Acho que o log4j também usa o ThreadLocal para manter o MDC.
ThreadLocal
é útil quando você deseja ter algum estado que não deve ser compartilhado entre diferentes threads, mas deve ser acessível a partir de cada thread durante toda a sua vida útil.
Como exemplo, imagine um aplicativo Web, em que cada solicitação é atendida por um encadeamento diferente. Imagine que, para cada solicitação, você precise de um pedaço de dados várias vezes, o que é bastante caro para calcular. No entanto, esses dados podem ter sido alterados para cada solicitação recebida, o que significa que você não pode usar um cache simples. Uma solução simples e rápida para esse problema seria ter uma ThreadLocal
variável mantendo acesso a esses dados, para que você precise calculá-los apenas uma vez para cada solicitação. Obviamente, esse problema também pode ser resolvido sem o uso deThreadLocal
, mas eu o projetei para fins ilustrativos.
Dito isto, tenha em mente que ThreadLocal
s são essencialmente uma forma de estado global. Como resultado, ele tem muitas outras implicações e deve ser usado somente após considerar todas as outras soluções possíveis.
ThreadLocal
como campo de objeto ...
ThreadLocal
, onde era acessível globalmente. Como você disse, ainda pode ser usado como um campo de classe que limita a visibilidade.
Nada realmente novo aqui, mas descobri hoje que ThreadLocal
é muito útil ao usar a Validação de Bean em um aplicativo da web. As mensagens de validação são localizadas, mas por padrão são usadas Locale.getDefault()
. Você pode configurar o Validator
com um diferente MessageInterpolator
, mas não há como especificar o Locale
quando você chama validate
. Portanto, você pode criar uma estática ThreadLocal<Locale>
(ou, melhor ainda, um contêiner geral com outras coisas que talvez você precise ser ThreadLocal
e, em seguida, MessageInterpolator
escolher o Locale
que é personalizado. O próximo passo é escrever um ServletFilter
que use um valor de sessão ou request.getLocale()
escolher o local e armazenar no seuThreadLocal
referência.
Como foi mencionado por @unknown (google), seu uso é definir uma variável global na qual o valor referenciado pode ser único em cada thread. Seus usos normalmente envolvem o armazenamento de algum tipo de informação contextual vinculada ao encadeamento atual de execução.
Nós o usamos em um ambiente Java EE para passar a identidade do usuário para classes que não têm conhecimento do Java EE (não têm acesso ao HttpSession ou ao EJB SessionContext). Dessa forma, o código, que faz uso da identidade para operações baseadas em segurança, pode acessar a identidade de qualquer lugar, sem ter que passar explicitamente em todas as chamadas de método.
O ciclo de operações de solicitação / resposta na maioria das chamadas Java EE facilita esse tipo de uso, pois fornece pontos de entrada e saída bem definidos para definir e desabilitar o ThreadLocal.
O ThreadLocal garantirá que o acesso ao objeto mutável pelos vários threads no método não sincronizado seja sincronizado, significa tornar o objeto mutável imutável no método.
Isso é obtido dando uma nova instância de objeto mutável para cada thread que tenta acessá-lo. Portanto, é uma cópia local para cada segmento. Isso é um truque para tornar a variável de instância em um método a ser acessado como uma variável local. Como você sabe que a variável local do método está disponível apenas para o encadeamento, uma diferença é; As variáveis locais do método não estarão disponíveis para o encadeamento quando a execução do método terminar, onde o objeto mutável compartilhado com o threadlocal estará disponível em vários métodos até que seja limpo.
Por definição:
A classe ThreadLocal em Java permite criar variáveis que podem ser lidas e gravadas apenas pelo mesmo encadeamento. Portanto, mesmo que dois threads estejam executando o mesmo código e o código tenha uma referência a uma variável ThreadLocal, os dois threads não poderão ver as variáveis ThreadLocal um do outro.
Cada um Thread
em java contém ThreadLocalMap
nele.
Onde
Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.
Atingindo o ThreadLocal:
Agora crie uma classe de wrapper para o ThreadLocal que manterá o objeto mutável como abaixo (com ou sem initialValue()
).
Agora, o getter e o setter deste wrapper funcionarão na instância threadlocal em vez de no objeto mutável.
Se getter () de threadlocal não encontrou nenhum valor no threadlocalmap do Thread
; em seguida, ele chamará o initialValue () para obter sua cópia particular em relação ao encadeamento.
class SimpleDateFormatInstancePerThread {
private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
UUID id = UUID.randomUUID();
@Override
public String toString() {
return id.toString();
};
};
System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
return dateFormat;
}
};
/*
* Every time there is a call for DateFormat, ThreadLocal will return calling
* Thread's copy of SimpleDateFormat
*/
public static DateFormat getDateFormatter() {
return dateFormatHolder.get();
}
public static void cleanup() {
dateFormatHolder.remove();
}
}
Agora wrapper.getDateFormatter()
chamará threadlocal.get()
e isso verificará a instância currentThread.threadLocalMap
contém esta (threadlocal).
Se sim, retorne o valor (SimpleDateFormat) para a instância threadlocal correspondente, caso
contrário, adicione o mapa com essa instância threadlocal, initialValue ().
Com isso, a segurança do thread é alcançada nessa classe mutável; por cada thread está trabalhando com sua própria instância mutável, mas com a mesma instância ThreadLocal. Significa que todo o encadeamento compartilhará a mesma instância ThreadLocal da chave, mas diferentes instâncias SimpleDateFormat como valor.
https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java
As variáveis locais de encadeamento são frequentemente usadas para impedir o compartilhamento em projetos baseados em Singletons mutáveis ou variáveis globais.
Ele pode ser usado em cenários como fazer uma conexão JDBC separada para cada encadeamento quando você não estiver usando um Conjunto de Conexões.
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
Quando você chama getConnection, ele retornará uma conexão associada a esse thread. O mesmo pode ser feito com outras propriedades como dateformat, contexto de transação que você não deseja compartilhar entre threads.
Você também pode ter usado variáveis locais para o mesmo, mas esses recursos geralmente demoram na criação, portanto, você não deseja criá-las repetidamente sempre que executar alguma lógica de negócios com elas. No entanto, os valores ThreadLocal são armazenados no próprio objeto de encadeamento e, assim que o encadeamento é coletado de lixo, esses valores desaparecem também.
Este link explica muito bem o uso do ThreadLocal.
A classe ThreadLocal em Java permite criar variáveis que podem ser lidas e gravadas apenas pelo mesmo encadeamento. Portanto, mesmo que dois threads estejam executando o mesmo código e o código tenha uma referência a uma variável ThreadLocal, os dois threads não poderão ver as variáveis ThreadLocal um do outro.
Existem três cenários para usar um auxiliar de classe como SimpleDateFormat no código multithread, o melhor é usar ThreadLocal
Cenários
1- Usando o objeto de compartilhamento com a ajuda do mecanismo de bloqueio ou sincronização , que torna o aplicativo lento
2- Usando como objeto local dentro de um método
Nesse cenário, se tivermos 4 encadeamentos, cada um chamará um método 1000 vezes, teremos
4000 objetos SimpleDateFormat criados e aguardando o GC apagá-los
3- Usando ThreadLocal
se tivermos 4 threads e demos a cada thread uma instância SimpleDateFormat
, teremos 4 threads , 4 objetos de SimpleDateFormat.
Não há necessidade de mecanismo de bloqueio e criação e destruição de objetos. (Boa complexidade de tempo e complexidade de espaço)
[Para referência] O ThreadLocal não pode resolver problemas de atualização do objeto compartilhado. É recomendável usar um objeto staticThreadLocal que seja compartilhado por todas as operações no mesmo encadeamento. O método [Obrigatório] remove () deve ser implementado pelas variáveis ThreadLocal, especialmente ao usar conjuntos de encadeamentos nos quais os encadeamentos geralmente são reutilizados. Caso contrário, isso pode afetar a lógica comercial subsequente e causar problemas inesperados, como vazamento de memória.
Armazenamento em cache, em algum momento você deve calcular o mesmo valor por muito tempo, armazenando o último conjunto de entradas em um método e o resultado pode acelerar o código. Ao usar o Thread Local Storage, você evita pensar em travar.
ThreadLocal é uma funcionalidade especialmente provisionada pela JVM para fornecer um espaço de armazenamento isolado apenas para threads. como o valor da variável com escopo da instância, são vinculados a uma determinada instância apenas de uma classe. cada objeto tem seus únicos valores e eles não podem se ver. assim como o conceito de variáveis ThreadLocal, elas são locais para o segmento, no sentido de instâncias de objeto, outro segmento, exceto aquele que o criou, não pode vê-lo. Veja aqui
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
public static void main(String[] args) {
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
new Thread(() -> IntStream.range(1, 3).forEach(i -> {
System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
})).start();
}
}
O Threadlocal fornece uma maneira muito fácil de obter a reutilização de objetos com custo zero.
Eu tive uma situação em que vários threads estavam criando uma imagem de cache mutável, em cada notificação de atualização.
Usei um Threadlocal em cada thread e, em seguida, cada thread precisaria redefinir a imagem antiga e atualizá-la novamente a partir do cache em cada notificação de atualização.
Objetos reutilizáveis comuns de conjuntos de objetos têm um custo de segurança de encadeamento associado a eles, enquanto essa abordagem não possui nenhum.
Experimente este pequeno exemplo, para ter uma idéia da variável ThreadLocal:
public class Book implements Runnable {
private static final ThreadLocal<List<String>> WORDS = ThreadLocal.withInitial(ArrayList::new);
private final String bookName; // It is also the thread's name
private final List<String> words;
public Book(String bookName, List<String> words) {
this.bookName = bookName;
this.words = Collections.unmodifiableList(words);
}
public void run() {
WORDS.get().addAll(words);
System.out.printf("Result %s: '%s'.%n", bookName, String.join(", ", WORDS.get()));
}
public static void main(String[] args) {
Thread t1 = new Thread(new Book("BookA", Arrays.asList("wordA1", "wordA2", "wordA3")));
Thread t2 = new Thread(new Book("BookB", Arrays.asList("wordB1", "wordB2")));
t1.start();
t2.start();
}
}
Saída do console, se o encadeamento BookA for feito primeiro:
Resultado BookA: 'wordA1, wordA2, wordA3'.
Livro de resultadosB: 'wordB1, wordB2'.
Saída do console, se o encadeamento BookB for feito primeiro:
Resultado BookB: 'wordB1, wordB2'.
Livro de resultadosA: 'wordA1, wordA2, wordA3'.