¿Cuándo debo usar un ThreadLocal
variable?
¿Cómo se usa?
RequestUtils.getCurrentRequestThreadLocal()
. Sin embargo, esto no es muy elegante, pero esto se debe al hecho de que ThreadLocal en sí no es muy elegante en la mayoría de los casos.
¿Cuándo debo usar un ThreadLocal
variable?
¿Cómo se usa?
RequestUtils.getCurrentRequestThreadLocal()
. Sin embargo, esto no es muy elegante, pero esto se debe al hecho de que ThreadLocal en sí no es muy elegante en la mayoría de los casos.
Respuestas:
Un uso posible (y común) es cuando tiene algún objeto que no es seguro para subprocesos, pero desea evitar la sincronización del acceso a ese objeto (lo estoy mirando, SimpleDateFormat ). En cambio, dele a cada hilo su propia instancia del objeto.
Por ejemplo:
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
. Quizás sea mejor usar una alternativa segura para subprocesos . Si está de acuerdo en que los singletons son malos, entonces ThreadLocal
es aún peor.
Como a ThreadLocal
es una referencia a los datos dentro de un determinado Thread
, puede terminar con pérdidas de carga de clase cuando se usa ThreadLocal
s en servidores de aplicaciones que usan grupos de subprocesos. Tienes que ser muy cuidadosos con la limpieza de cualquier ThreadLocal
que S get()
o set()
mediante el uso de los ThreadLocal
'sremove()
método.
Si no limpia cuando haya terminado, cualquier referencia que contenga a clases cargadas como parte de una aplicación web desplegada permanecerá en el montón permanente y nunca se recolectará basura. Volver a desplegar / desplegar la aplicación web no limpiará Thread
la referencia de cada uno a la (s) clase (s) de su aplicación web ya Thread
que no es algo propiedad de su aplicación web. Cada implementación sucesiva creará una nueva instancia de la clase que nunca se recolectará basura.
Terminará con las excepciones de memoria insuficiente debido a que, java.lang.OutOfMemoryError: PermGen space
después de buscar en Google, probablemente solo aumente en -XX:MaxPermSize
lugar de corregir el error.
Si finalmente experimenta estos problemas, puede determinar qué hilo y clase retiene estas referencias utilizando el Analizador de memoria de Eclipse y / o siguiendo la guía y el seguimiento de Frank Kieviet .
Actualización: Re-descubrí la entrada del blog de Alex Vasseur que me ayudó a localizar algunos ThreadLocal
problemas que estaba teniendo.
Muchos marcos usan ThreadLocals para mantener un contexto relacionado con el hilo actual. Por ejemplo, cuando la transacción actual se almacena en un ThreadLocal, no necesita pasarla como un parámetro a través de cada llamada al método, en caso de que alguien de la pila necesite acceder a ella. Las aplicaciones web pueden almacenar información sobre la solicitud actual y la sesión en un ThreadLocal, para que la aplicación tenga fácil acceso a ellas. Con Guice puede usar ThreadLocals al implementar ámbitos personalizados para los objetos inyectados (los ámbitos de servlet predeterminados de Guice probablemente también los usen).
ThreadLocals son un tipo de variables globales (aunque un poco menos malvadas porque están restringidas a un hilo), por lo que debe tener cuidado al usarlas para evitar efectos secundarios no deseados y pérdidas de memoria. Diseñe sus API para que los valores de ThreadLocal siempre se borren automáticamente cuando ya no se necesiten y el uso incorrecto de la API no sea posible (por ejemplo, así ). ThreadLocals se puede usar para hacer que el código sea más limpio, y en algunos casos raros son la única forma de hacer que algo funcione (mi proyecto actual tenía dos de estos casos; se documentan aquí en "Campos estáticos y variables globales").
En Java, si tiene un dato que puede variar por hilo, sus opciones son pasar ese dato a cada método que lo necesite (o pueda necesitarlo), o asociar el dato con el hilo. Pasar el dato por todas partes puede ser viable si todos sus métodos ya necesitan pasar una variable de "contexto" común.
Si ese no es el caso, es posible que no desee saturar las firmas de sus métodos con un parámetro adicional. En un mundo sin hilos, podría resolver el problema con el equivalente Java de una variable global. En una palabra enhebrada, el equivalente de una variable global es una variable de hilo local.
'datum' is the singular form and 'data' is the plural form.
Hay un muy buen ejemplo en el libro Java Concurrency in Practice . Donde el autor ( Joshua Bloch ) explica cómo el confinamiento de hilos es una de las formas más simples de lograr la seguridad y ThreadLocal es un medio más formal para mantener el confinamiento de hilos. Al final, también explica cómo las personas pueden abusar de él al usarlo como variables globales.
Copié el texto del libro mencionado, pero falta el código 3.10, ya que no es muy importante entender dónde se debe usar ThreadLocal.
Las variables locales de subprocesos a menudo se usan para evitar compartir en diseños basados en Singletons mutables o variables globales. Por ejemplo, una aplicación de subproceso único puede mantener una conexión de base de datos global que se inicializa al inicio para evitar tener que pasar una conexión a cada método. Dado que las conexiones JDBC pueden no ser seguras para subprocesos, una aplicación multiproceso que utiliza una conexión global sin coordinación adicional tampoco es segura para subprocesos. Al usar un ThreadLocal para almacenar la conexión JDBC, como en ConnectionHolder en el Listado 3.10, cada hilo tendrá su propia conexión.
ThreadLocal se usa ampliamente en la implementación de marcos de aplicaciones. Por ejemplo, los contenedores J2EE asocian un contexto de transacción con un subproceso en ejecución durante la duración de una llamada EJB. Esto se implementa fácilmente usando un Thread-Local estático que contiene el contexto de la transacción: cuando el código marco necesita determinar qué transacción se está ejecutando actualmente, obtiene el contexto de la transacción de este ThreadLocal. Esto es conveniente porque reduce la necesidad de pasar información de contexto de ejecución a cada método, pero combina cualquier código que use este mecanismo al marco.
Es fácil abusar de ThreadLocal tratando su propiedad de confinamiento de hilos como una licencia para usar variables globales o como un medio para crear argumentos de métodos "ocultos". Al igual que las variables globales, las variables locales de subprocesos pueden restar valor a la reutilización e introducir acoplamientos ocultos entre las clases y, por lo tanto, deben usarse con cuidado.
Esencialmente, cuando necesita el valor de una variable para depender del subproceso actual y no le conviene adjuntar el valor al subproceso de alguna otra manera (por ejemplo, subclasificar subprocesos).
Un caso típico es cuando algún otro marco ha creado el hilo en el que se está ejecutando su código, por ejemplo, un contenedor de servlet, o donde tiene más sentido usar ThreadLocal porque su variable está "en su lugar lógico" (en lugar de una variable colgando de una subclase de Thread o en algún otro mapa hash).
En mi sitio web, tengo más discusión y ejemplos de cuándo usar ThreadLocal que también pueden ser de interés.
Algunas personas abogan por usar ThreadLocal como una forma de adjuntar un "ID de subproceso" a cada subproceso en ciertos algoritmos concurrentes donde necesita un número de subproceso (ver, por ejemplo, Herlihy y Shavit). En tales casos, compruebe que realmente está obteniendo un beneficio.
ThreadLocal en Java se había introducido en JDK 1.2, pero luego se generó en JDK 1.5 para introducir la seguridad de tipos en la variable ThreadLocal.
ThreadLocal se puede asociar con el alcance de Thread, todo el código que ejecuta Thread tiene acceso a las variables ThreadLocal, pero dos hilos no pueden ver la variable ThreadLocal.
Cada subproceso contiene una copia exclusiva de la variable ThreadLocal que se vuelve elegible para la recolección de elementos no utilizados después de que el subproceso finalizó o murió, normalmente o debido a cualquier excepción, dado que la variable ThreadLocal no tiene ninguna otra referencia en vivo.
Las variables ThreadLocal en Java son generalmente campos estáticos privados en las clases y mantienen su estado dentro de Thread.
Leer más: ThreadLocal en Java - Ejemplo de programa y tutorial
La documentación dice muy bien: "cada subproceso que accede a [una variable de subproceso local] (a través de su método get o set) tiene su propia copia de la variable inicializada independientemente".
Utiliza uno cuando cada hilo debe tener su propia copia de algo. Por defecto, los datos se comparten entre hilos.
Dos casos de uso en los que se puede usar la variable threadlocal:
1- Cuando tenemos un requisito para asociar el estado con un hilo (por ejemplo, una ID de usuario o ID de transacción). Eso suele suceder con una aplicación web en la que cada solicitud que va a un servlet tiene un ID de transacción único asociado.
// 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();
}
}
Tenga en cuenta que aquí el método withInitial se implementa utilizando la expresión lambda.
2- Otro caso de uso es cuando queremos tener una instancia segura para subprocesos y no queremos usar la sincronización ya que el costo de rendimiento con la sincronización es mayor. Uno de esos casos es cuando se usa SimpleDateFormat. Como SimpleDateFormat no es seguro para subprocesos, debemos proporcionar un mecanismo para que sea seguro para subprocesos.
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 el lanzamiento de Java 8, hay una forma más declarativa de inicializar ThreadLocal
:
ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");
Hasta el lanzamiento de Java 8, tenía que hacer lo siguiente:
ThreadLocal<String> local = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "init value";
}
};
Además, si el método de creación de instancias (constructor, método de fábrica) de la clase utilizada ThreadLocal
no toma ningún parámetro, simplemente puede usar referencias de métodos (introducidas en Java 8):
class NotThreadSafe {
// no parameters
public NotThreadSafe(){}
}
ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);
Nota: La
evaluación es perezosa ya que está pasando java.util.function.Supplier
lambda que se evalúa solo cuando ThreadLocal#get
se llama pero el valor no se evaluó previamente.
Tienes que tener mucho cuidado con el patrón ThreadLocal. Hay algunos inconvenientes importantes como Phil mencionó, pero uno que no se mencionó es asegurarse de que el código que configura el contexto ThreadLocal no sea "reentrante".
Pueden ocurrir cosas malas cuando el código que establece la información se ejecuta por segunda o tercera vez porque la información en su hilo puede comenzar a mutar cuando no lo esperaba. Así que asegúrese de asegurarse de que la información de ThreadLocal no se haya configurado antes de volver a configurarla.
ThreadLocal
patrón. Si lo hace F(){ member=random(); F2(); write(member); }
y F2 anula al miembro con un nuevo valor, entonces obviamente write(member)
ya no escribirá el número que ha random()
editado. Esto es literalmente solo sentido común. Del mismo modo, si lo hace F(){ F(); }
, ¡ tenga suerte con su bucle infinito! Esto es cierto en todas partes y no es específico para ThreadLocal
.
cuando?
Cuando un objeto no es seguro para subprocesos, en lugar de la sincronización que dificulta la escalabilidad, dele un objeto a cada subproceso y manténgalo dentro del alcance del subproceso, que es ThreadLocal. Uno de los objetos más utilizados pero no seguros para subprocesos es Database Connection y JMSConnection.
Cómo ?
Un ejemplo es que Spring Framework usa ThreadLocal en gran medida para administrar transacciones detrás de escena manteniendo estos objetos de conexión en las variables ThreadLocal. En un nivel alto, cuando se inicia una transacción, obtiene la conexión (y deshabilita la confirmación automática) y la mantiene en ThreadLocal. en otras llamadas db, usa la misma conexión para comunicarse con db. Al final, toma la conexión de ThreadLocal y confirma (o revierte) la transacción y libera la conexión.
Creo que log4j también usa ThreadLocal para mantener MDC.
ThreadLocal
es útil cuando desea tener un estado que no debería compartirse entre diferentes subprocesos, pero debería ser accesible desde cada subproceso durante toda su vida útil.
Como ejemplo, imagine una aplicación web, donde cada solicitud es atendida por un hilo diferente. Imagine que para cada solicitud necesita un dato varias veces, lo cual es bastante costoso de calcular. Sin embargo, esos datos pueden haber cambiado para cada solicitud entrante, lo que significa que no puede usar una memoria caché simple. Una solución simple y rápida para este problema sería tener una ThreadLocal
variable que tenga acceso a estos datos, de modo que tenga que calcularlos solo una vez para cada solicitud. Por supuesto, este problema también se puede resolver sin el uso deThreadLocal
, pero lo ideé con fines ilustrativos.
Dicho esto, tenga en cuenta que los ThreadLocal
s son esencialmente una forma de estado global. Como resultado, tiene muchas otras implicaciones y debe usarse solo después de considerar todas las otras posibles soluciones.
ThreadLocal
como campo de objeto ...
ThreadLocal
, donde era accesible a nivel mundial. Como dijiste, todavía se puede usar como un campo de clase que limita la visibilidad.
No hay nada realmente nuevo aquí, pero descubrí hoy que ThreadLocal
es muy útil cuando se usa Bean Validation en una aplicación web. Los mensajes de validación están localizados, pero de forma predeterminada se usan Locale.getDefault()
. Puede configurar el Validator
con un diferente MessageInterpolator
, pero no hay forma de especificar Locale
cuándo llamar validate
. Por lo tanto, puede crear un archivo estático ThreadLocal<Locale>
(o mejor aún, un contenedor general con otras cosas que podría necesitar ser ThreadLocal
y luego hacer que su MessageInterpolator
selección personalizada Locale
de eso. El siguiente paso es escribir un ServletFilter
que use un valor de sesión o request.getLocale()
elegir la configuración regional y almacenar en tu ThreadLocal
referencia.
Como mencionó @unknown (google), su uso es definir una variable global en la que el valor al que se hace referencia puede ser único en cada hilo. Sus usos generalmente implican almacenar algún tipo de información contextual que está vinculada al hilo actual de ejecución.
Lo usamos en un entorno Java EE para pasar la identidad del usuario a clases que no son conscientes de Java EE (no tienen acceso a HttpSession o al EJB SessionContext). De esta manera, el código, que utiliza la identidad para operaciones basadas en la seguridad, puede acceder a la identidad desde cualquier lugar, sin tener que pasarla explícitamente en cada llamada al método.
El ciclo de operaciones de solicitud / respuesta en la mayoría de las llamadas Java EE hace que este tipo de uso sea fácil, ya que proporciona puntos de entrada y salida bien definidos para configurar y desactivar ThreadLocal.
ThreadLocal se asegurará de que el acceso al objeto mutable por los múltiples hilos en el método no sincronizado esté sincronizado, lo que significa que el objeto mutable sea inmutable dentro del método.
Esto se logra dando una nueva instancia de objeto mutable para cada subproceso intente acceder a él. Por lo tanto, es una copia local en cada hilo. Este es un truco para hacer que la instancia sea variable en un método al que se pueda acceder como una variable local. Como sabe, la variable local del método solo está disponible para el hilo, una diferencia es; las variables locales del método no estarán disponibles para el subproceso una vez que finalice la ejecución del método, donde un objeto mutable compartido con threadlocal estará disponible en varios métodos hasta que lo limpiemos.
Por definición:
La clase ThreadLocal en Java le permite crear variables que solo pueden ser leídas y escritas por el mismo hilo. Por lo tanto, incluso si dos hilos están ejecutando el mismo código, y el código tiene una referencia a una variable ThreadLocal, entonces los dos hilos no pueden ver las variables ThreadLocal del otro.
Cada uno Thread
en Java contiene ThreadLocalMap
en él.
Dónde
Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.
Logrando el hilo Local:
Ahora cree una clase de contenedor para ThreadLocal que contendrá el objeto mutable como se muestra a continuación (con o sin initialValue()
).
Ahora getter y setter de este contenedor funcionarán en una instancia de threadlocal en lugar de un objeto mutable.
Si getter () de threadlocal no encontró ningún valor con en el threadlocalmap de Thread
; luego invocará initialValue () para obtener su copia privada con respecto al hilo.
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();
}
}
Ahora wrapper.getDateFormatter()
llamará threadlocal.get()
y eso comprobará que currentThread.threadLocalMap
contiene esta instancia (threadlocal).
En caso afirmativo, devuelva el valor (SimpleDateFormat) para la instancia de threadlocal correspondiente; de lo
contrario, agregue el mapa con esta instancia de threadlocal, initialValue ().
Con esto se logra la seguridad del hilo en esta clase mutable; por cada hilo está trabajando con su propia instancia mutable pero con la misma instancia de ThreadLocal. Significa que todo el hilo compartirá la misma instancia de ThreadLocal que la clave, pero diferente instancia de SimpleDateFormat como valor.
https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java
Las variables locales de subproceso a menudo se usan para evitar compartir en diseños basados en Singletons mutables o variables globales.
Se puede usar en escenarios como hacer una conexión JDBC separada para cada subproceso cuando no está utilizando un Pool de conexiones.
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
Cuando llame a getConnection, devolverá una conexión asociada con ese subproceso. Lo mismo se puede hacer con otras propiedades como dateformat, contexto de transacción que no desea compartir entre subprocesos.
También podría haber utilizado variables locales para el mismo, pero estos recursos generalmente requieren tiempo en la creación, por lo que no desea crearlos una y otra vez cada vez que realice alguna lógica comercial con ellos. Sin embargo, los valores ThreadLocal se almacenan en el propio objeto de subproceso y, tan pronto como se recoge el subproceso, estos valores también desaparecen.
Este enlace explica muy bien el uso de ThreadLocal.
La clase ThreadLocal en Java le permite crear variables que solo pueden ser leídas y escritas por el mismo hilo. Por lo tanto, incluso si dos hilos están ejecutando el mismo código, y el código tiene una referencia a una variable ThreadLocal, entonces los dos hilos no pueden ver las variables ThreadLocal del otro.
Hay 3 escenarios para usar un ayudante de clase como SimpleDateFormat en código multiproceso, el mejor es usar ThreadLocal
Escenarios
1- Uso de un objeto compartido similar con la ayuda del mecanismo de bloqueo o sincronización que hace que la aplicación sea lenta
2- Usar como un objeto local dentro de un método
En este escenario, si tenemos 4 hilos cada uno llama a un método 1000 veces, entonces tenemos
4000 objetos SimpleDateFormat creados y esperando que GC los borre
3- Usando ThreadLocal
si tenemos 4 subprocesos y le damos a cada subproceso una instancia de SimpleDateFormat
para que tengamos 4 subprocesos , 4 objetos de SimpleDateFormat.
No hay necesidad de mecanismo de bloqueo y creación y destrucción de objetos. (Buena complejidad temporal y complejidad espacial)
[Para referencia] ThreadLocal no puede resolver problemas de actualización de objetos compartidos. Se recomienda utilizar un objeto staticThreadLocal que todas las operaciones comparten en el mismo hilo. El método [obligatorio] remove () debe ser implementado por las variables ThreadLocal, especialmente cuando se usan grupos de subprocesos en los que los subprocesos a menudo se reutilizan. De lo contrario, puede afectar la lógica empresarial posterior y causar problemas inesperados, como pérdida de memoria.
Almacenamiento en caché, en algún momento debe calcular el mismo valor mucho tiempo, por lo que al almacenar el último conjunto de entradas en un método y el resultado, puede acelerar el código. Al usar Thread Local Storage, evitas tener que pensar en bloquear.
ThreadLocal es una funcionalidad especialmente aprovisionada por JVM para proporcionar un espacio de almacenamiento aislado solo para subprocesos. como el valor de la variable de ámbito de instancia, están vinculados a una instancia dada de una clase solamente. cada objeto tiene sus únicos valores y no pueden verse entre sí. también lo es el concepto de variables ThreadLocal, que son locales al hilo en el sentido de instancias de objeto que otro hilo, excepto el que lo creó, no puede verlo. Mira aquí
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();
}
}
Threadlocal proporciona una manera muy fácil de lograr la reutilización de objetos con costo cero.
Tuve una situación en la que varios subprocesos creaban una imagen de caché mutable en cada notificación de actualización.
Usé un Threadlocal en cada hilo, y luego cada hilo solo necesitaría restablecer la imagen anterior y luego actualizarla nuevamente desde el caché en cada notificación de actualización.
Los objetos reutilizables habituales de los grupos de objetos tienen un costo de seguridad de subprocesos asociado a ellos, mientras que este enfoque no tiene ninguno.
Pruebe este pequeño ejemplo para tener una idea de la variable 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();
}
}
Salida de la consola, si el hilo BookA se realiza primero:
Libro de resultados A: 'wordA1, wordA2, wordA3'.
Libro de resultadosB: 'palabraB1, palabraB2'.
Salida de la consola, si el hilo BookB se hace primero:
Libro de resultadosB: 'wordB1, wordB2'.
Libro de resultados A: 'wordA1, wordA2, wordA3'.