Concurrencia de Java - Guía rápida
Java es un lenguaje de programación multiproceso, lo que significa que podemos desarrollar un programa multiproceso usando Java. Un programa de subprocesos múltiples contiene dos o más partes que pueden ejecutarse simultáneamente y cada parte puede manejar una tarea diferente al mismo tiempo haciendo un uso óptimo de los recursos disponibles, especialmente cuando su computadora tiene múltiples CPU.
Por definición, la multitarea es cuando varios procesos comparten recursos de procesamiento comunes, como una CPU. El subproceso múltiple extiende la idea de la multitarea a aplicaciones en las que puede subdividir operaciones específicas dentro de una sola aplicación en subprocesos individuales. Cada uno de los subprocesos se puede ejecutar en paralelo. El sistema operativo divide el tiempo de procesamiento no solo entre diferentes aplicaciones, sino también entre cada hilo dentro de una aplicación.
El subproceso múltiple le permite escribir de una manera en la que pueden realizarse múltiples actividades simultáneamente en el mismo programa.
Ciclo de vida de un hilo
Un hilo pasa por varias etapas en su ciclo de vida. Por ejemplo, un hilo nace, se inicia, se ejecuta y luego muere. El siguiente diagrama muestra el ciclo de vida completo de un hilo.
Las siguientes son las etapas del ciclo de vida:
New- Un nuevo hilo comienza su ciclo de vida en el nuevo estado. Permanece en este estado hasta que el programa inicia el hilo. También se conoce comoborn thread.
Runnable- Después de que se inicia un subproceso recién nacido, el subproceso se vuelve ejecutable. Se considera que un subproceso en este estado está ejecutando su tarea.
Waiting- A veces, un hilo pasa al estado de espera mientras el hilo espera a que otro hilo realice una tarea. Un subproceso vuelve al estado ejecutable solo cuando otro subproceso indica al subproceso en espera que continúe ejecutándose.
Timed Waiting- Un hilo ejecutable puede entrar en el estado de espera temporizado durante un intervalo de tiempo especificado. Un hilo en este estado vuelve al estado ejecutable cuando ese intervalo de tiempo expira o cuando ocurre el evento que está esperando.
Terminated (Dead) - Un subproceso ejecutable entra en el estado terminado cuando completa su tarea o termina.
Prioridades de hilo
Cada hilo de Java tiene una prioridad que ayuda al sistema operativo a determinar el orden en el que se programan los hilos.
Las prioridades de los subprocesos de Java están en el rango entre MIN_PRIORITY (una constante de 1) y MAX_PRIORITY (una constante de 10). De forma predeterminada, cada hilo tiene prioridad NORM_PRIORITY (una constante de 5).
Los subprocesos con mayor prioridad son más importantes para un programa y se les debe asignar tiempo de procesador antes que los subprocesos de menor prioridad. Sin embargo, las prioridades de los subprocesos no pueden garantizar el orden en el que se ejecutan los subprocesos y dependen en gran medida de la plataforma.
Crear un hilo implementando una interfaz ejecutable
Si su clase está destinada a ejecutarse como un hilo, puede lograrlo implementando un Runnableinterfaz. Deberá seguir tres pasos básicos:
Paso 1
Como primer paso, debe implementar un método run () proporcionado por un Runnableinterfaz. Este método proporciona un punto de entrada para el hilo y pondrá su lógica empresarial completa dentro de este método. A continuación se muestra una sintaxis simple del método run ():
public void run( )
Paso 2
Como segundo paso, creará una instancia Thread objeto usando el siguiente constructor -
Thread(Runnable threadObj, String threadName);
Donde, threadObj es una instancia de una clase que implementa elRunnable interfaz y threadName es el nombre que se le da al nuevo hilo.
Paso 3
Una vez que se crea un objeto Thread, puede iniciarlo llamando start()método, que ejecuta una llamada al método run (). A continuación se muestra una sintaxis simple del método start ():
void start();
Example
Aquí hay un ejemplo que crea un nuevo hilo y comienza a ejecutarlo:
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo(String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo("Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo("Thread-2");
R2.start();
}
}
Esto producirá el siguiente resultado:
Output
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
Crear un hilo ampliando una clase de hilo
La segunda forma de crear un hilo es crear una nueva clase que se extienda Threadclase usando los siguientes dos sencillos pasos. Este enfoque proporciona más flexibilidad en el manejo de varios subprocesos creados con los métodos disponibles en la clase Thread.
Paso 1
Necesitarás anular run( )método disponible en la clase Thread. Este método proporciona un punto de entrada para el hilo y pondrá su lógica empresarial completa dentro de este método. A continuación se muestra una sintaxis simple del método run ():
public void run( )
Paso 2
Una vez que se crea el objeto Thread, puede iniciarlo llamando start()método, que ejecuta una llamada al método run (). A continuación se muestra una sintaxis simple del método start ():
void start( );
Example
Aquí está el programa anterior reescrito para extender el hilo:
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo(String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo("Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo("Thread-2");
T2.start();
}
}
Esto producirá el siguiente resultado:
Output
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
En este capítulo, discutiremos los diferentes aspectos de la configuración de un entorno agradable para Java.
Configuración del entorno local
Si aún está dispuesto a configurar su entorno para el lenguaje de programación Java, esta sección lo guía sobre cómo descargar y configurar Java en su máquina. Los siguientes son los pasos para configurar el entorno.
Java SE está disponible gratuitamente desde el enlace Descargar Java . Puede descargar una versión basada en su sistema operativo.
Siga las instrucciones para descargar Java y ejecutar el .exepara instalar Java en su máquina. Una vez que haya instalado Java en su máquina, deberá configurar las variables de entorno para que apunten a los directorios de instalación correctos:
Configuración de la ruta para Windows
Suponiendo que haya instalado Java en el directorio c: \ Archivos de programa \ java \ jdk -
Haga clic derecho en 'Mi PC' y seleccione 'Propiedades'.
Haga clic en el botón 'Variables de entorno' en la pestaña 'Avanzado'.
Ahora, modifique la variable 'Ruta' para que también contenga la ruta al ejecutable de Java. Por ejemplo, si la ruta está configurada actualmente en 'C: \ WINDOWS \ SYSTEM32', cambie su ruta para que lea 'C: \ WINDOWS \ SYSTEM32; c: \ Archivos de programa \ java \ jdk \ bin'.
Configuración de la ruta para Linux, UNIX, Solaris, FreeBSD
La variable de entorno PATH debe establecerse para que apunte a dónde se han instalado los binarios de Java. Consulte la documentación de su shell, si tiene problemas para hacer esto.
Por ejemplo, si usa bash como su shell, entonces agregaría la siguiente línea al final de su '.bashrc: export PATH = / path / to / java: $ PATH'
Editores populares de Java
Para escribir sus programas Java, necesitará un editor de texto. Hay IDE aún más sofisticados disponibles en el mercado. Pero por ahora, puede considerar uno de los siguientes:
Notepad - En una máquina con Windows, puede usar cualquier editor de texto simple como el Bloc de notas (recomendado para este tutorial), TextPad.
Netbeans - Un IDE de Java que es de código abierto y gratuito que se puede descargar desde https://netbeans.org/index.html.
Eclipse - Un IDE de Java desarrollado por la comunidad de código abierto eclipse y se puede descargar desde https://www.eclipse.org/.
Core Java proporciona un control completo sobre el programa multiproceso. Puede desarrollar un programa multiproceso que se puede suspender, reanudar o detener por completo según sus requisitos. Existen varios métodos estáticos que puede utilizar en objetos de hilo para controlar su comportamiento. La siguiente tabla enumera esos métodos:
No Señor. | Método y descripción |
---|---|
1 | public void suspend() Este método pone un hilo en estado suspendido y se puede reanudar usando el método resume (). |
2 | public void stop() Este método detiene un hilo por completo. |
3 | public void resume() Este método reanuda un hilo, que fue suspendido usando el método suspend (). |
4 | public void wait() Hace que el hilo actual espere hasta que otro hilo invoque el aviso (). |
5 | public void notify() Despierta un único subproceso que está esperando en el monitor de este objeto. |
Tenga en cuenta que las últimas versiones de Java han desaprobado el uso de los métodos suspend (), resume () y stop (), por lo que debe utilizar las alternativas disponibles.
Ejemplo
class RunnableDemo implements Runnable {
public Thread t;
private String threadName;
boolean suspended = false;
RunnableDemo(String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 10; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Let the thread sleep for a while.
Thread.sleep(300);
synchronized(this) {
while(suspended) {
wait();
}
}
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
void suspend() {
suspended = true;
}
synchronized void resume() {
suspended = false;
notify();
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo("Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo("Thread-2");
R2.start();
try {
Thread.sleep(1000);
R1.suspend();
System.out.println("Suspending First Thread");
Thread.sleep(1000);
R1.resume();
System.out.println("Resuming First Thread");
R2.suspend();
System.out.println("Suspending thread Two");
Thread.sleep(1000);
R2.resume();
System.out.println("Resuming thread Two");
} catch (InterruptedException e) {
System.out.println("Main thread Interrupted");
} try {
System.out.println("Waiting for threads to finish.");
R1.t.join();
R2.t.join();
} catch (InterruptedException e) {
System.out.println("Main thread Interrupted");
}
System.out.println("Main thread exiting.");
}
}
El programa anterior produce la siguiente salida:
Salida
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 10
Running Thread-2
Thread: Thread-2, 10
Thread: Thread-1, 9
Thread: Thread-2, 9
Thread: Thread-1, 8
Thread: Thread-2, 8
Thread: Thread-1, 7
Thread: Thread-2, 7
Suspending First Thread
Thread: Thread-2, 6
Thread: Thread-2, 5
Thread: Thread-2, 4
Resuming First Thread
Suspending thread Two
Thread: Thread-1, 6
Thread: Thread-1, 5
Thread: Thread-1, 4
Thread: Thread-1, 3
Resuming thread Two
Thread: Thread-2, 3
Waiting for threads to finish.
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
Main thread exiting.
Si conoce la comunicación entre procesos, le resultará fácil comprender la comunicación entre hilos. La comunicación entre subprocesos es importante cuando se desarrolla una aplicación en la que dos o más subprocesos intercambian información.
Hay tres métodos simples y un pequeño truco que hace posible la comunicación por hilo. Los tres métodos se enumeran a continuación:
No Señor. | Método y descripción |
---|---|
1 | public void wait() Hace que el hilo actual espere hasta que otro hilo invoque el aviso (). |
2 | public void notify() Despierta un único subproceso que está esperando en el monitor de este objeto. |
3 | public void notifyAll() Despierta todos los hilos que llamaron a wait () en el mismo objeto. |
Estos métodos se han implementado como finalmétodos en Object, por lo que están disponibles en todas las clases. Los tres métodos solo se pueden llamar desde dentro de unsynchronized contexto.
Ejemplo
Este ejemplo muestra cómo dos hilos pueden comunicarse usando wait() y notify()método. Puede crear un sistema complejo utilizando el mismo concepto.
class Chat {
boolean flag = false;
public synchronized void Question(String msg) {
if (flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(msg);
flag = true;
notify();
}
public synchronized void Answer(String msg) {
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(msg);
flag = false;
notify();
}
}
class T1 implements Runnable {
Chat m;
String[] s1 = { "Hi", "How are you ?", "I am also doing fine!" };
public T1(Chat m1) {
this.m = m1;
new Thread(this, "Question").start();
}
public void run() {
for (int i = 0; i < s1.length; i++) {
m.Question(s1[i]);
}
}
}
class T2 implements Runnable {
Chat m;
String[] s2 = { "Hi", "I am good, what about you?", "Great!" };
public T2(Chat m2) {
this.m = m2;
new Thread(this, "Answer").start();
}
public void run() {
for (int i = 0; i < s2.length; i++) {
m.Answer(s2[i]);
}
}
}
public class TestThread {
public static void main(String[] args) {
Chat m = new Chat();
new T1(m);
new T2(m);
}
}
Cuando se cumple y ejecuta el programa anterior, produce el siguiente resultado:
Salida
Hi
Hi
How are you ?
I am good, what about you?
I am also doing fine!
Great!
El ejemplo anterior se tomó y luego se modificó de [https://stackoverflow.com/questions/2170520/inter-thread-communication-in-java]
Ejemplo de subprocesos múltiples con sincronización
Aquí está el mismo ejemplo que imprime el valor del contador en secuencia y cada vez que lo ejecutamos, produce el mismo resultado.
Ejemplo
class PrintDemo {
public void printCount() {
try {
for(int i = 5; i > 0; i--) {
System.out.println("Counter --- " + i );
}
} catch (Exception e) {
System.out.println("Thread interrupted.");
}
}
}
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
PrintDemo PD;
ThreadDemo(String name, PrintDemo pd) {
threadName = name;
PD = pd;
}
public void run() {
synchronized(PD) {
PD.printCount();
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
PrintDemo PD = new PrintDemo();
ThreadDemo T1 = new ThreadDemo("Thread - 1 ", PD);
ThreadDemo T2 = new ThreadDemo("Thread - 2 ", PD);
T1.start();
T2.start();
// wait for threads to end
try {
T1.join();
T2.join();
} catch (Exception e) {
System.out.println("Interrupted");
}
}
}
Esto produce el mismo resultado cada vez que ejecuta este programa:
Salida
Starting Thread - 1
Starting Thread - 2
Counter --- 5
Counter --- 4
Counter --- 3
Counter --- 2
Counter --- 1
Thread Thread - 1 exiting.
Counter --- 5
Counter --- 4
Counter --- 3
Counter --- 2
Counter --- 1
Thread Thread - 2 exiting.
Deadlock describe una situación en la que dos o más subprocesos están bloqueados para siempre, esperando el uno al otro. El interbloqueo ocurre cuando varios subprocesos necesitan los mismos bloqueos pero los obtienen en un orden diferente. Un programa Java multiproceso puede sufrir la condición de interbloqueo porque elsynchronizedLa palabra clave hace que el subproceso en ejecución se bloquee mientras espera el bloqueo o monitor asociado con el objeto especificado. Aquí hay un ejemplo.
Ejemplo
public class TestThread {
public static Object Lock1 = new Object();
public static Object Lock2 = new Object();
public static void main(String args[]) {
ThreadDemo1 T1 = new ThreadDemo1();
ThreadDemo2 T2 = new ThreadDemo2();
T1.start();
T2.start();
}
private static class ThreadDemo1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Holding lock 1...");
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
}
private static class ThreadDemo2 extends Thread {
public void run() {
synchronized (Lock2) {
System.out.println("Thread 2: Holding lock 2...");
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (Lock1) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}
}
}
Cuando compila y ejecuta el programa anterior, encuentra una situación de interbloqueo y la siguiente es la salida producida por el programa:
Salida
Thread 1: Holding lock 1...
Thread 2: Holding lock 2...
Thread 1: Waiting for lock 2...
Thread 2: Waiting for lock 1...
El programa anterior se bloqueará para siempre porque ninguno de los hilos está en posición de continuar y esperando que el otro libere el bloqueo, por lo que puede salir del programa presionando CTRL + C.
Ejemplo de solución de interbloqueo
Cambiemos el orden del bloqueo y ejecutemos el mismo programa para ver si ambos hilos aún se esperan el uno al otro -
Ejemplo
public class TestThread {
public static Object Lock1 = new Object();
public static Object Lock2 = new Object();
public static void main(String args[]) {
ThreadDemo1 T1 = new ThreadDemo1();
ThreadDemo2 T2 = new ThreadDemo2();
T1.start();
T2.start();
}
private static class ThreadDemo1 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 1: Holding lock 1...");
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
}
}
private static class ThreadDemo2 extends Thread {
public void run() {
synchronized (Lock1) {
System.out.println("Thread 2: Holding lock 1...");
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for lock 2...");
synchronized (Lock2) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
}
}
}
Entonces, simplemente cambiar el orden de los bloqueos evita que el programa entre en una situación de punto muerto y se completa con el siguiente resultado:
Salida
Thread 1: Holding lock 1...
Thread 1: Waiting for lock 2...
Thread 1: Holding lock 1 & 2...
Thread 2: Holding lock 1...
Thread 2: Waiting for lock 2...
Thread 2: Holding lock 1 & 2...
El ejemplo anterior es solo para aclarar el concepto, sin embargo, es un concepto complejo y debe profundizar en él antes de desarrollar sus aplicaciones para lidiar con situaciones de bloqueo.
La clase ThreadLocal se utiliza para crear variables locales de subprocesos que solo pueden ser leídas y escritas por el mismo subproceso. Por ejemplo, si dos subprocesos acceden al código que hace referencia a la misma variable threadLocal, cada subproceso no verá ninguna modificación a la variable threadLocal realizada por otro subproceso.
Métodos ThreadLocal
A continuación se muestra la lista de métodos importantes disponibles en la clase ThreadLocal.
No Señor. | Método y descripción |
---|---|
1 | public T get() Devuelve el valor en la copia del hilo actual de esta variable local del hilo. |
2 | protected T initialValue() Devuelve el "valor inicial" del hilo actual para esta variable local del hilo. |
3 | public void remove() Elimina el valor del hilo actual para esta variable local del hilo. |
4 | public void set(T value) Establece la copia del hilo actual de esta variable local del hilo en el valor especificado. |
Ejemplo
El siguiente programa TestThread muestra algunos de estos métodos de la clase ThreadLocal. Aquí hemos usado dos variables de contador, una es variable normal y otra es ThreadLocal.
class RunnableDemo implements Runnable {
int counter;
ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<Integer>();
public void run() {
counter++;
if(threadLocalCounter.get() != null) {
threadLocalCounter.set(threadLocalCounter.get().intValue() + 1);
} else {
threadLocalCounter.set(0);
}
System.out.println("Counter: " + counter);
System.out.println("threadLocalCounter: " + threadLocalCounter.get());
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo commonInstance = new RunnableDemo();
Thread t1 = new Thread(commonInstance);
Thread t2 = new Thread(commonInstance);
Thread t3 = new Thread(commonInstance);
Thread t4 = new Thread(commonInstance);
t1.start();
t2.start();
t3.start();
t4.start();
// wait for threads to end
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (Exception e) {
System.out.println("Interrupted");
}
}
}
Esto producirá el siguiente resultado.
Salida
Counter: 1
threadLocalCounter: 0
Counter: 2
threadLocalCounter: 0
Counter: 3
threadLocalCounter: 0
Counter: 4
threadLocalCounter: 0
Puede ver que el valor del contador aumenta con cada hilo, pero threadLocalCounter sigue siendo 0 para cada hilo.
Un java.util.concurrent.ThreadLocalRandom es una clase de utilidad introducida desde jdk 1.7 en adelante y es útil cuando se requieren múltiples subprocesos o ForkJoinTasks para generar números aleatorios. Mejora el rendimiento y tiene menos contención que el método Math.random ().
Métodos ThreadLocalRandom
A continuación se muestra la lista de métodos importantes disponibles en la clase ThreadLocalRandom.
No Señor. | Método y descripción |
---|---|
1 | public static ThreadLocalRandom current() Devuelve el ThreadLocalRandom del hilo actual. |
2 | protected int next(int bits) Genera el siguiente número pseudoaleatorio. |
3 | public double nextDouble(double n) Devuelve un valor doble pseudoaleatorio y uniformemente distribuido entre 0 (inclusive) y el valor especificado (exclusivo). |
4 | public double nextDouble(double least, double bound) Devuelve un valor pseudoaleatorio, distribuido uniformemente entre el valor mínimo dado (incluido) y el límite (exclusivo). |
5 | public int nextInt(int least, int bound) Devuelve un valor pseudoaleatorio, distribuido uniformemente entre el valor mínimo dado (incluido) y el límite (exclusivo). |
6 | public long nextLong(long n) Devuelve un valor pseudoaleatorio distribuido uniformemente entre 0 (inclusive) y el valor especificado (exclusivo). |
7 | public long nextLong(long least, long bound) Devuelve un valor pseudoaleatorio, distribuido uniformemente entre el valor mínimo dado (incluido) y el límite (exclusivo). |
8 | public void setSeed(long seed) Lanza UnsupportedOperationException. |
Ejemplo
El siguiente programa TestThread demuestra algunos de estos métodos de la interfaz de bloqueo. Aquí hemos utilizado lock () para adquirir el bloqueo y desbloquear () para liberar el bloqueo.
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ThreadLocalRandom;
public class TestThread {
public static void main(final String[] arguments) {
System.out.println("Random Integer: " + new Random().nextInt());
System.out.println("Seeded Random Integer: " + new Random(15).nextInt());
System.out.println(
"Thread Local Random Integer: " + ThreadLocalRandom.current().nextInt());
final ThreadLocalRandom random = ThreadLocalRandom.current();
random.setSeed(15); //exception will come as seeding is not allowed in ThreadLocalRandom.
System.out.println("Seeded Thread Local Random Integer: " + random.nextInt());
}
}
Esto producirá el siguiente resultado.
Salida
Random Integer: 1566889198
Seeded Random Integer: -1159716814
Thread Local Random Integer: 358693993
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.concurrent.ThreadLocalRandom.setSeed(Unknown Source)
at TestThread.main(TestThread.java:21)
Aquí hemos utilizado las clases ThreadLocalRandom y Random para obtener números aleatorios.
Una interfaz java.util.concurrent.locks.Lock se utiliza como mecanismo de sincronización de subprocesos similar a los bloques sincronizados. El nuevo mecanismo de bloqueo es más flexible y ofrece más opciones que un bloque sincronizado. Las principales diferencias entre un bloqueo y un bloque sincronizado son las siguientes:
Guarantee of sequence- El bloque sincronizado no ofrece ninguna garantía de secuencia en la que se dará acceso al hilo en espera. La interfaz de bloqueo lo maneja.
No timeout- El bloqueo sincronizado no tiene opción de tiempo de espera si no se otorga el bloqueo. La interfaz de bloqueo proporciona dicha opción.
Single method - El bloque sincronizado debe estar completamente contenido dentro de un solo método, mientras que los métodos de una interfaz de bloqueo lock () y unlock () se pueden llamar en diferentes métodos.
Métodos de bloqueo
A continuación se muestra la lista de métodos importantes disponibles en la clase Lock.
No Señor. | Método y descripción |
---|---|
1 | public void lock() Adquiere la cerradura. |
2 | public void lockInterruptibly() Adquiere el bloqueo a menos que se interrumpa el hilo actual. |
3 | public Condition newCondition() Devuelve una nueva instancia de Condition que está vinculada a esta instancia de Lock. |
4 | public boolean tryLock() Adquiere el candado solo si está libre en el momento de la invocación. |
5 | public boolean tryLock() Adquiere el candado solo si está libre en el momento de la invocación. |
6 | public boolean tryLock(long time, TimeUnit unit) Adquiere el candado si está libre dentro del tiempo de espera dado y no se ha interrumpido el hilo actual. |
7 | public void unlock() Libera el candado. |
Ejemplo
El siguiente programa TestThread demuestra algunos de estos métodos de la interfaz de bloqueo. Aquí hemos utilizado lock () para adquirir el bloqueo y desbloquear () para liberar el bloqueo.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class PrintDemo {
private final Lock queueLock = new ReentrantLock();
public void print() {
queueLock.lock();
try {
Long duration = (long) (Math.random() * 10000);
System.out.println(Thread.currentThread().getName()
+ " Time Taken " + (duration / 1000) + " seconds.");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.printf(
"%s printed the document successfully.\n", Thread.currentThread().getName());
queueLock.unlock();
}
}
}
class ThreadDemo extends Thread {
PrintDemo printDemo;
ThreadDemo(String name, PrintDemo printDemo) {
super(name);
this.printDemo = printDemo;
}
@Override
public void run() {
System.out.printf(
"%s starts printing a document\n", Thread.currentThread().getName());
printDemo.print();
}
}
public class TestThread {
public static void main(String args[]) {
PrintDemo PD = new PrintDemo();
ThreadDemo t1 = new ThreadDemo("Thread - 1 ", PD);
ThreadDemo t2 = new ThreadDemo("Thread - 2 ", PD);
ThreadDemo t3 = new ThreadDemo("Thread - 3 ", PD);
ThreadDemo t4 = new ThreadDemo("Thread - 4 ", PD);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
Esto producirá el siguiente resultado.
Salida
Thread - 1 starts printing a document
Thread - 4 starts printing a document
Thread - 3 starts printing a document
Thread - 2 starts printing a document
Thread - 1 Time Taken 4 seconds.
Thread - 1 printed the document successfully.
Thread - 4 Time Taken 3 seconds.
Thread - 4 printed the document successfully.
Thread - 3 Time Taken 5 seconds.
Thread - 3 printed the document successfully.
Thread - 2 Time Taken 4 seconds.
Thread - 2 printed the document successfully.
Aquí usamos la clase ReentrantLock como una implementación de la interfaz de bloqueo. La clase ReentrantLock permite que un hilo bloquee un método incluso si ya tiene el bloqueo en otro método.
Una interfaz java.util.concurrent.locks.ReadWriteLock permite que varios subprocesos se lean a la vez, pero solo un subproceso puede escribir a la vez.
Read Lock - Si ningún subproceso ha bloqueado ReadWriteLock para escritura, entonces varios subprocesos pueden acceder al bloqueo de lectura.
Write Lock - Si ningún hilo está leyendo o escribiendo, entonces un hilo puede acceder al bloqueo de escritura.
Métodos de bloqueo
A continuación se muestra la lista de métodos importantes disponibles en la clase Lock.
No Señor. | Método y descripción |
---|---|
1 | public Lock readLock() Devuelve el candado utilizado para la lectura. |
2 | public Lock writeLock() Devuelve el candado utilizado para escribir. |
Ejemplo
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestThread {
private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
private static String message = "a";
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new WriterA());
t1.setName("Writer A");
Thread t2 = new Thread(new WriterB());
t2.setName("Writer B");
Thread t3 = new Thread(new Reader());
t3.setName("Reader");
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
}
static class Reader implements Runnable {
public void run() {
if(lock.isWriteLocked()) {
System.out.println("Write Lock Present.");
}
lock.readLock().lock();
try {
Long duration = (long) (Math.random() * 10000);
System.out.println(Thread.currentThread().getName()
+ " Time Taken " + (duration / 1000) + " seconds.");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() +": "+ message );
lock.readLock().unlock();
}
}
}
static class WriterA implements Runnable {
public void run() {
lock.writeLock().lock();
try {
Long duration = (long) (Math.random() * 10000);
System.out.println(Thread.currentThread().getName()
+ " Time Taken " + (duration / 1000) + " seconds.");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
message = message.concat("a");
lock.writeLock().unlock();
}
}
}
static class WriterB implements Runnable {
public void run() {
lock.writeLock().lock();
try {
Long duration = (long) (Math.random() * 10000);
System.out.println(Thread.currentThread().getName()
+ " Time Taken " + (duration / 1000) + " seconds.");
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
message = message.concat("b");
lock.writeLock().unlock();
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Writer A Time Taken 6 seconds.
Write Lock Present.
Writer B Time Taken 2 seconds.
Reader Time Taken 0 seconds.
Reader: aab
Una interfaz java.util.concurrent.locks.Condition proporciona una capacidad de subproceso para suspender su ejecución, hasta que la condición dada sea verdadera. Un objeto Condition está necesariamente vinculado a un Lock y debe obtenerse mediante el método newCondition ().
Métodos de condición
A continuación se muestra la lista de métodos importantes disponibles en la clase Condition.
No Señor. | Método y descripción |
---|---|
1 | public void await() Hace que el hilo actual espere hasta que sea señalado o interrumpido. |
2 | public boolean await(long time, TimeUnit unit) Hace que el hilo actual espere hasta que se señalice o se interrumpa, o hasta que transcurra el tiempo de espera especificado. |
3 | public long awaitNanos(long nanosTimeout) Hace que el hilo actual espere hasta que se señalice o se interrumpa, o hasta que transcurra el tiempo de espera especificado. |
4 | public long awaitUninterruptibly() Hace que el hilo actual espere hasta que se señale. |
5 | public long awaitUntil() Hace que el hilo actual espere hasta que sea señalado o interrumpido, o hasta que transcurra la fecha límite especificada. |
6 | public void signal() Despierta un hilo en espera. |
7 | public void signalAll() Despierta todos los hilos en espera. |
Ejemplo
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestThread {
public static void main(String[] args) throws InterruptedException {
ItemQueue itemQueue = new ItemQueue(10);
//Create a producer and a consumer.
Thread producer = new Producer(itemQueue);
Thread consumer = new Consumer(itemQueue);
//Start both threads.
producer.start();
consumer.start();
//Wait for both threads to terminate.
producer.join();
consumer.join();
}
static class ItemQueue {
private Object[] items = null;
private int current = 0;
private int placeIndex = 0;
private int removeIndex = 0;
private final Lock lock;
private final Condition isEmpty;
private final Condition isFull;
public ItemQueue(int capacity) {
this.items = new Object[capacity];
lock = new ReentrantLock();
isEmpty = lock.newCondition();
isFull = lock.newCondition();
}
public void add(Object item) throws InterruptedException {
lock.lock();
while(current >= items.length)
isFull.await();
items[placeIndex] = item;
placeIndex = (placeIndex + 1) % items.length;
++current;
//Notify the consumer that there is data available.
isEmpty.signal();
lock.unlock();
}
public Object remove() throws InterruptedException {
Object item = null;
lock.lock();
while(current <= 0) {
isEmpty.await();
}
item = items[removeIndex];
removeIndex = (removeIndex + 1) % items.length;
--current;
//Notify the producer that there is space available.
isFull.signal();
lock.unlock();
return item;
}
public boolean isEmpty() {
return (items.length == 0);
}
}
static class Producer extends Thread {
private final ItemQueue queue;
public Producer(ItemQueue queue) {
this.queue = queue;
}
@Override
public void run() {
String[] numbers =
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"};
try {
for(String number: numbers) {
System.out.println("[Producer]: " + number);
}
queue.add(null);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
static class Consumer extends Thread {
private final ItemQueue queue;
public Consumer(ItemQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
do {
Object number = queue.remove();
System.out.println("[Consumer]: " + number);
if(number == null) {
return;
}
} while(!queue.isEmpty());
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
Esto producirá el siguiente resultado.
Salida
[Producer]: 1
[Producer]: 2
[Producer]: 3
[Producer]: 4
[Producer]: 5
[Producer]: 6
[Producer]: 7
[Producer]: 8
[Producer]: 9
[Producer]: 10
[Producer]: 11
[Producer]: 12
[Consumer]: null
Una clase java.util.concurrent.atomic.AtomicInteger proporciona operaciones sobre el valor int subyacente que se puede leer y escribir de forma atómica, y también contiene operaciones atómicas avanzadas. AtomicInteger admite operaciones atómicas en la variable int subyacente. Tiene métodos get y set que funcionan como lectura y escritura en variables volátiles. Es decir, un conjunto tiene una relación de suceder antes con cualquier obtención posterior de la misma variable. El método atomic compareAndSet también tiene estas características de coherencia de memoria.
Métodos AtomicInteger
A continuación se muestra la lista de métodos importantes disponibles en la clase AtomicInteger.
No Señor. | Método y descripción |
---|---|
1 | public int addAndGet(int delta) Agrega atómicamente el valor dado al valor actual. |
2 | public boolean compareAndSet(int expect, int update) Atómicamente establece el valor en el valor actualizado dado si el valor actual es el mismo que el valor esperado. |
3 | public int decrementAndGet() Disminuye atómicamente en uno el valor actual. |
4 | public double doubleValue() Devuelve el valor del número especificado como doble. |
5 | public float floatValue() Devuelve el valor del número especificado como flotante. |
6 | public int get() Obtiene el valor actual. |
7 | public int getAndAdd(int delta) Atómicamente agrega el valor dado al valor actual. |
8 | public int getAndDecrement() Disminuye atómicamente en uno el valor actual. |
9 | public int getAndIncrement() Incrementa atómicamente en uno el valor actual. |
10 | public int getAndSet(int newValue) Atómicamente se establece en el valor dado y devuelve el valor anterior. |
11 | public int incrementAndGet() Incrementa atómicamente en uno el valor actual. |
12 | public int intValue() Devuelve el valor del número especificado como int. |
13 | public void lazySet(int newValue) Eventualmente se establece en el valor dado. |
14 | public long longValue() Devuelve el valor del número especificado como long. |
15 | public void set(int newValue) Establece el valor dado. |
dieciséis | public String toString() Devuelve la representación de cadena del valor actual. |
17 | public boolean weakCompareAndSet(int expect, int update) Atómicamente establece el valor en el valor actualizado dado si el valor actual es el mismo que el valor esperado. |
Ejemplo
El siguiente programa TestThread muestra una implementación insegura de contador en un entorno basado en subprocesos.
public class TestThread {
static class Counter {
private int c = 0;
public void increment() {
c++;
}
public int value() {
return c;
}
}
public static void main(final String[] arguments) throws InterruptedException {
final Counter counter = new Counter();
//1000 threads
for(int i = 0; i < 1000 ; i++) {
new Thread(new Runnable() {
public void run() {
counter.increment();
}
}).start();
}
Thread.sleep(6000);
System.out.println("Final number (should be 1000): " + counter.value());
}
}
Esto puede producir el siguiente resultado según la velocidad de la computadora y el entrelazado de hilos.
Salida
Final number (should be 1000): 1000
Ejemplo
import java.util.concurrent.atomic.AtomicInteger;
public class TestThread {
static class Counter {
private AtomicInteger c = new AtomicInteger(0);
public void increment() {
c.getAndIncrement();
}
public int value() {
return c.get();
}
}
public static void main(final String[] arguments) throws InterruptedException {
final Counter counter = new Counter();
//1000 threads
for(int i = 0; i < 1000 ; i++) {
new Thread(new Runnable() {
public void run() {
counter.increment();
}
}).start();
}
Thread.sleep(6000);
System.out.println("Final number (should be 1000): " + counter.value());
}
}
Esto producirá el siguiente resultado.
Salida
Final number (should be 1000): 1000
Una clase java.util.concurrent.atomic.AtomicLong proporciona operaciones sobre un valor largo subyacente que se puede leer y escribir de forma atómica, y también contiene operaciones atómicas avanzadas. AtomicLong admite operaciones atómicas en una variable larga subyacente. Tiene métodos get y set que funcionan como lectura y escritura en variables volátiles. Es decir, un conjunto tiene una relación de suceder antes con cualquier obtención posterior de la misma variable. El método atomic compareAndSet también tiene estas características de coherencia de memoria.
Métodos AtomicLong
A continuación se muestra la lista de métodos importantes disponibles en la clase AtomicLong.
No Señor. | Método y descripción |
---|---|
1 | public long addAndGet(long delta) Agrega atómicamente el valor dado al valor actual. |
2 | public boolean compareAndSet(long expect, long update) Atómicamente establece el valor en el valor actualizado dado si el valor actual es el mismo que el valor esperado. |
3 | public long decrementAndGet() Disminuye atómicamente en uno el valor actual. |
4 | public double doubleValue() Devuelve el valor del número especificado como doble. |
5 | public float floatValue() Devuelve el valor del número especificado como flotante. |
6 | public long get() Obtiene el valor actual. |
7 | public long getAndAdd(long delta) Atómicamente agrega el valor dado al valor actual. |
8 | public long getAndDecrement() Disminuye atómicamente en uno el valor actual. |
9 | public long getAndIncrement() Incrementa atómicamente en uno el valor actual. |
10 | public long getAndSet(long newValue) Atómicamente se establece en el valor dado y devuelve el valor anterior. |
11 | public long incrementAndGet() Incrementa atómicamente en uno el valor actual. |
12 | public int intValue() Devuelve el valor del número especificado como int. |
13 | public void lazySet(long newValue) Eventualmente se establece en el valor dado. |
14 | public long longValue() Devuelve el valor del número especificado como long. |
15 | public void set(long newValue) Establece el valor dado. |
dieciséis | public String toString() Devuelve la representación de cadena del valor actual. |
17 | public boolean weakCompareAndSet(long expect, long update) Atómicamente establece el valor en el valor actualizado dado si el valor actual es el mismo que el valor esperado. |
Ejemplo
El siguiente programa TestThread muestra una implementación segura del contador usando AtomicLong en un entorno basado en subprocesos.
import java.util.concurrent.atomic.AtomicLong;
public class TestThread {
static class Counter {
private AtomicLong c = new AtomicLong(0);
public void increment() {
c.getAndIncrement();
}
public long value() {
return c.get();
}
}
public static void main(final String[] arguments) throws InterruptedException {
final Counter counter = new Counter();
//1000 threads
for(int i = 0; i < 1000 ; i++) {
new Thread(new Runnable() {
public void run() {
counter.increment();
}
}).start();
}
Thread.sleep(6000);
System.out.println("Final number (should be 1000): " + counter.value());
}
}
Esto producirá el siguiente resultado.
Salida
Final number (should be 1000): 1000
Una clase java.util.concurrent.atomic.AtomicBoolean proporciona operaciones sobre un valor booleano subyacente que se puede leer y escribir de forma atómica, y también contiene operaciones atómicas avanzadas. AtomicBoolean admite operaciones atómicas en una variable booleana subyacente. Tiene métodos get y set que funcionan como lectura y escritura en variables volátiles. Es decir, un conjunto tiene una relación de suceder antes con cualquier obtención posterior de la misma variable. El método atomic compareAndSet también tiene estas características de coherencia de memoria.
Métodos AtomicBooleanos
A continuación se muestra la lista de métodos importantes disponibles en la clase AtomicBoolean.
No Señor. | Método y descripción |
---|---|
1 | public boolean compareAndSet(boolean expect, boolean update) Atómicamente establece el valor en el valor actualizado dado si el valor actual == el valor esperado. |
2 | public boolean get() Devuelve el valor actual. |
3 | public boolean getAndSet(boolean newValue) Atómicamente se establece en el valor dado y devuelve el valor anterior. |
4 | public void lazySet(boolean newValue) Eventualmente se establece en el valor dado. |
5 | public void set(boolean newValue) Establece incondicionalmente al valor dado. |
6 | public String toString() Devuelve la representación de cadena del valor actual. |
7 | public boolean weakCompareAndSet(boolean expect, boolean update) Atómicamente establece el valor en el valor actualizado dado si el valor actual == el valor esperado. |
Ejemplo
El siguiente programa TestThread muestra el uso de la variable AtomicBoolean en un entorno basado en subprocesos.
import java.util.concurrent.atomic.AtomicBoolean;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
new Thread("Thread 1") {
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName()
+" Waiting for Thread 2 to set Atomic variable to true. Current value is "
+ atomicBoolean.get());
if(atomicBoolean.compareAndSet(true, false)) {
System.out.println("Done!");
break;
}
}
};
}.start();
new Thread("Thread 2") {
public void run() {
System.out.println(Thread.currentThread().getName() +
", Atomic Variable: " +atomicBoolean.get());
System.out.println(Thread.currentThread().getName() +
" is setting the variable to true ");
atomicBoolean.set(true);
System.out.println(Thread.currentThread().getName() +
", Atomic Variable: " +atomicBoolean.get());
};
}.start();
}
}
Esto producirá el siguiente resultado.
Salida
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 2, Atomic Variable: false
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Thread 2 is setting the variable to true
Thread 2, Atomic Variable: true
Thread 1 Waiting for Thread 2 to set Atomic variable to true. Current value is false
Done!
Una clase java.util.concurrent.atomic.AtomicReference proporciona operaciones en una referencia de objeto subyacente que se puede leer y escribir de forma atómica, y también contiene operaciones atómicas avanzadas. AtomicReference admite operaciones atómicas en la variable de referencia de objeto subyacente. Tiene métodos get y set que funcionan como lectura y escritura en variables volátiles. Es decir, un conjunto tiene una relación de suceder antes con cualquier obtención posterior de la misma variable. El método atomic compareAndSet también tiene estas características de coherencia de memoria.
Métodos de referencia atómica
A continuación se muestra la lista de métodos importantes disponibles en la clase AtomicReference.
No Señor. | Método y descripción |
---|---|
1 | public boolean compareAndSet(V expect, V update) Atómicamente establece el valor en el valor actualizado dado si el valor actual == el valor esperado. |
2 | public boolean get() Devuelve el valor actual. |
3 | public boolean getAndSet(V newValue) Atómicamente se establece en el valor dado y devuelve el valor anterior. |
4 | public void lazySet(V newValue) Eventualmente se establece en el valor dado. |
5 | public void set(V newValue) Establece incondicionalmente al valor dado. |
6 | public String toString() Devuelve la representación de cadena del valor actual. |
7 | public boolean weakCompareAndSet(V expect, V update) Atómicamente establece el valor en el valor actualizado dado si el valor actual == el valor esperado. |
Ejemplo
El siguiente programa TestThread muestra el uso de la variable AtomicReference en un entorno basado en subprocesos.
import java.util.concurrent.atomic.AtomicReference;
public class TestThread {
private static String message = "hello";
private static AtomicReference<String> atomicReference;
public static void main(final String[] arguments) throws InterruptedException {
atomicReference = new AtomicReference<String>(message);
new Thread("Thread 1") {
public void run() {
atomicReference.compareAndSet(message, "Thread 1");
message = message.concat("-Thread 1!");
};
}.start();
System.out.println("Message is: " + message);
System.out.println("Atomic Reference of Message is: " + atomicReference.get());
}
}
Esto producirá el siguiente resultado.
Salida
Message is: hello
Atomic Reference of Message is: Thread 1
Una clase java.util.concurrent.atomic.AtomicIntegerArray proporciona operaciones en una matriz int subyacente que se puede leer y escribir de forma atómica, y también contiene operaciones atómicas avanzadas. AtomicIntegerArray admite operaciones atómicas en la variable de matriz int subyacente. Tiene métodos get y set que funcionan como lectura y escritura en variables volátiles. Es decir, un conjunto tiene una relación de suceder antes con cualquier obtención posterior de la misma variable. El método atomic compareAndSet también tiene estas características de coherencia de memoria.
Métodos AtomicIntegerArray
A continuación se muestra la lista de métodos importantes disponibles en la clase AtomicIntegerArray.
No Señor. | Método y descripción |
---|---|
1 | public int addAndGet(int i, int delta) Agrega atómicamente el valor dado al elemento en el índice i. |
2 | public boolean compareAndSet(int i, int expect, int update) Atómicamente establece el elemento en la posición i al valor actualizado dado si el valor actual == el valor esperado. |
3 | public int decrementAndGet(int i) Disminuye atómicamente en uno el elemento en el índice i. |
4 | public int get(int i) Obtiene el valor actual en la posición i. |
5 | public int getAndAdd(int i, int delta) Agrega atómicamente el valor dado al elemento en el índice i. |
6 | public int getAndDecrement(int i) Disminuye atómicamente en uno el elemento en el índice i. |
7 | public int getAndIncrement(int i) Incrementa atómicamente en uno el elemento en el índice i. |
8 | public int getAndSet(int i, int newValue) Atómicamente establece el elemento en la posición i al valor dado y devuelve el valor anterior. |
9 | public int incrementAndGet(int i) Incrementa atómicamente en uno el elemento en el índice i. |
10 | public void lazySet(int i, int newValue) Eventualmente establece el elemento en la posición i al valor dado. |
11 | public int length() Devuelve la longitud de la matriz. |
12 | public void set(int i, int newValue) Establece el elemento en la posición i en el valor dado. |
13 | public String toString() Devuelve la representación de cadena de los valores actuales de la matriz. |
14 | public boolean weakCompareAndSet(int i, int expect, int update) Atómicamente establece el elemento en la posición i al valor actualizado dado si el valor actual == el valor esperado. |
Ejemplo
El siguiente programa TestThread muestra el uso de la variable AtomicIntegerArray en un entorno basado en subprocesos.
import java.util.concurrent.atomic.AtomicIntegerArray;
public class TestThread {
private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
public static void main(final String[] arguments) throws InterruptedException {
for (int i = 0; i<atomicIntegerArray.length(); i++) {
atomicIntegerArray.set(i, 1);
}
Thread t1 = new Thread(new Increment());
Thread t2 = new Thread(new Compare());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Values: ");
for (int i = 0; i<atomicIntegerArray.length(); i++) {
System.out.print(atomicIntegerArray.get(i) + " ");
}
}
static class Increment implements Runnable {
public void run() {
for(int i = 0; i<atomicIntegerArray.length(); i++) {
int add = atomicIntegerArray.incrementAndGet(i);
System.out.println("Thread " + Thread.currentThread().getId()
+ ", index " +i + ", value: "+ add);
}
}
}
static class Compare implements Runnable {
public void run() {
for(int i = 0; i<atomicIntegerArray.length(); i++) {
boolean swapped = atomicIntegerArray.compareAndSet(i, 2, 3);
if(swapped) {
System.out.println("Thread " + Thread.currentThread().getId()
+ ", index " +i + ", value: 3");
}
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Thread 10, index 0, value: 2
Thread 10, index 1, value: 2
Thread 10, index 2, value: 2
Thread 11, index 0, value: 3
Thread 10, index 3, value: 2
Thread 11, index 1, value: 3
Thread 11, index 2, value: 3
Thread 10, index 4, value: 2
Thread 11, index 3, value: 3
Thread 10, index 5, value: 2
Thread 10, index 6, value: 2
Thread 11, index 4, value: 3
Thread 10, index 7, value: 2
Thread 11, index 5, value: 3
Thread 10, index 8, value: 2
Thread 11, index 6, value: 3
Thread 10, index 9, value: 2
Thread 11, index 7, value: 3
Thread 11, index 8, value: 3
Thread 11, index 9, value: 3
Values:
3 3 3 3 3 3 3 3 3 3
Una clase java.util.concurrent.atomic.AtomicLongArray proporciona operaciones en una matriz larga subyacente que se puede leer y escribir de forma atómica, y también contiene operaciones atómicas avanzadas. AtomicLongArray admite operaciones atómicas en la variable de matriz larga subyacente. Tiene métodos get y set que funcionan como lectura y escritura en variables volátiles. Es decir, un conjunto tiene una relación de suceder antes con cualquier obtención posterior de la misma variable. El método atomic compareAndSet también tiene estas características de coherencia de memoria.
Métodos AtomicLongArray
A continuación se muestra la lista de métodos importantes disponibles en la clase AtomicLongArray.
No Señor. | Método y descripción |
---|---|
1 | public long addAndGet(int i, long delta) Agrega atómicamente el valor dado al elemento en el índice i. |
2 | public boolean compareAndSet(int i, long expect, long update) Atómicamente establece el elemento en la posición i al valor actualizado dado si el valor actual == el valor esperado. |
3 | public long decrementAndGet(int i) Disminuye atómicamente en uno el elemento en el índice i. |
4 | public long get(int i) Obtiene el valor actual en la posición i. |
5 | public long getAndAdd(int i, long delta) Agrega atómicamente el valor dado al elemento en el índice i. |
6 | public long getAndDecrement(int i) Disminuye atómicamente en uno el elemento en el índice i. |
7 | public long getAndIncrement(int i) Incrementa atómicamente en uno el elemento en el índice i. |
8 | public long getAndSet(int i, long newValue) Atómicamente establece el elemento en la posición i al valor dado y devuelve el valor anterior. |
9 | public long incrementAndGet(int i) Incrementa atómicamente en uno el elemento en el índice i. |
10 | public void lazySet(int i, long newValue) Eventualmente establece el elemento en la posición i al valor dado. |
11 | public int length() Devuelve la longitud de la matriz. |
12 | public void set(int i, long newValue) Establece el elemento en la posición i en el valor dado. |
13 | public String toString() Devuelve la representación de cadena de los valores actuales de la matriz. |
14 | public boolean weakCompareAndSet(int i, long expect, long update) Atómicamente establece el elemento en la posición i al valor actualizado dado si el valor actual == el valor esperado. |
Ejemplo
El siguiente programa TestThread muestra el uso de la variable AtomicIntegerArray en un entorno basado en subprocesos.
import java.util.concurrent.atomic.AtomicLongArray;
public class TestThread {
private static AtomicLongArray atomicLongArray = new AtomicLongArray(10);
public static void main(final String[] arguments) throws InterruptedException {
for (int i = 0; i<atomicLongArray.length(); i++) {
atomicLongArray.set(i, 1);
}
Thread t1 = new Thread(new Increment());
Thread t2 = new Thread(new Compare());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Values: ");
for (int i = 0; i<atomicLongArray.length(); i++) {
System.out.print(atomicLongArray.get(i) + " ");
}
}
static class Increment implements Runnable {
public void run() {
for(int i = 0; i<atomicLongArray.length(); i++) {
long add = atomicLongArray.incrementAndGet(i);
System.out.println("Thread " + Thread.currentThread().getId()
+ ", index " +i + ", value: "+ add);
}
}
}
static class Compare implements Runnable {
public void run() {
for(int i = 0; i<atomicLongArray.length(); i++) {
boolean swapped = atomicLongArray.compareAndSet(i, 2, 3);
if(swapped) {
System.out.println("Thread " + Thread.currentThread().getId()
+ ", index " +i + ", value: 3");
}
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Thread 9, index 0, value: 2
Thread 10, index 0, value: 3
Thread 9, index 1, value: 2
Thread 9, index 2, value: 2
Thread 9, index 3, value: 2
Thread 9, index 4, value: 2
Thread 10, index 1, value: 3
Thread 9, index 5, value: 2
Thread 10, index 2, value: 3
Thread 9, index 6, value: 2
Thread 10, index 3, value: 3
Thread 9, index 7, value: 2
Thread 10, index 4, value: 3
Thread 9, index 8, value: 2
Thread 9, index 9, value: 2
Thread 10, index 5, value: 3
Thread 10, index 6, value: 3
Thread 10, index 7, value: 3
Thread 10, index 8, value: 3
Thread 10, index 9, value: 3
Values:
3 3 3 3 3 3 3 3 3 3
Una clase java.util.concurrent.atomic.AtomicReferenceArray proporciona operaciones en una matriz de referencia subyacente que se puede leer y escribir de forma atómica, y también contiene operaciones atómicas avanzadas. AtomicReferenceArray admite operaciones atómicas en la variable de matriz de referencia subyacente. Tiene métodos get y set que funcionan como lectura y escritura en variables volátiles. Es decir, un conjunto tiene una relación de suceder antes con cualquier obtención posterior de la misma variable. El método atomic compareAndSet también tiene estas características de coherencia de memoria.
Métodos AtomicReferenceArray
A continuación se muestra la lista de métodos importantes disponibles en la clase AtomicReferenceArray.
No Señor. | Método y descripción |
---|---|
1 | public boolean compareAndSet(int i, E expect, E update) Atómicamente establece el elemento en la posición i al valor actualizado dado si el valor actual == el valor esperado. |
2 | public E get(int i) Obtiene el valor actual en la posición i. |
3 | public E getAndSet(int i, E newValue) Atómicamente establece el elemento en la posición i al valor dado y devuelve el valor anterior. |
4 | public void lazySet(int i, E newValue) Eventualmente establece el elemento en la posición i al valor dado. |
5 | public int length() Devuelve la longitud de la matriz. |
6 | public void set(int i, E newValue) Establece el elemento en la posición i en el valor dado. |
7 | public String toString() Devuelve la representación de cadena de los valores actuales de la matriz. |
8 | public boolean weakCompareAndSet(int i, E expect, E update) Atómicamente establece el elemento en la posición i al valor actualizado dado si el valor actual == el valor esperado. |
Ejemplo
El siguiente programa TestThread muestra el uso de la variable AtomicReferenceArray en un entorno basado en subprocesos.
import java.util.concurrent.atomic.AtomicReferenceArray;
public class TestThread {
private static String[] source = new String[10];
private static AtomicReferenceArray<String> atomicReferenceArray
= new AtomicReferenceArray<String>(source);
public static void main(final String[] arguments) throws InterruptedException {
for (int i = 0; i<atomicReferenceArray.length(); i++) {
atomicReferenceArray.set(i, "item-2");
}
Thread t1 = new Thread(new Increment());
Thread t2 = new Thread(new Compare());
t1.start();
t2.start();
t1.join();
t2.join();
}
static class Increment implements Runnable {
public void run() {
for(int i = 0; i<atomicReferenceArray.length(); i++) {
String add = atomicReferenceArray.getAndSet(i,"item-"+ (i+1));
System.out.println("Thread " + Thread.currentThread().getId()
+ ", index " +i + ", value: "+ add);
}
}
}
static class Compare implements Runnable {
public void run() {
for(int i = 0; i<atomicReferenceArray.length(); i++) {
System.out.println("Thread " + Thread.currentThread().getId()
+ ", index " +i + ", value: "+ atomicReferenceArray.get(i));
boolean swapped = atomicReferenceArray.compareAndSet(i, "item-2", "updated-item-2");
System.out.println("Item swapped: " + swapped);
if(swapped) {
System.out.println("Thread " + Thread.currentThread().getId()
+ ", index " +i + ", updated-item-2");
}
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Thread 9, index 0, value: item-2
Thread 10, index 0, value: item-1
Item swapped: false
Thread 10, index 1, value: item-2
Item swapped: true
Thread 9, index 1, value: updated-item-2
Thread 10, index 1, updated-item-2
Thread 10, index 2, value: item-3
Item swapped: false
Thread 10, index 3, value: item-2
Item swapped: true
Thread 10, index 3, updated-item-2
Thread 10, index 4, value: item-2
Item swapped: true
Thread 10, index 4, updated-item-2
Thread 10, index 5, value: item-2
Item swapped: true
Thread 10, index 5, updated-item-2
Thread 10, index 6, value: item-2
Thread 9, index 2, value: item-2
Item swapped: true
Thread 9, index 3, value: updated-item-2
Thread 10, index 6, updated-item-2
Thread 10, index 7, value: item-2
Thread 9, index 4, value: updated-item-2
Item swapped: true
Thread 9, index 5, value: updated-item-2
Thread 10, index 7, updated-item-2
Thread 9, index 6, value: updated-item-2
Thread 10, index 8, value: item-2
Thread 9, index 7, value: updated-item-2
Item swapped: true
Thread 9, index 8, value: updated-item-2
Thread 10, index 8, updated-item-2
Thread 9, index 9, value: item-2
Thread 10, index 9, value: item-10
Item swapped: false
Una interfaz java.util.concurrent.Executor es una interfaz simple para apoyar el lanzamiento de nuevas tareas.
Métodos ExecutorService
No Señor. | Método y descripción |
---|---|
1 | void execute(Runnable command) Ejecuta el comando dado en algún momento en el futuro. |
Ejemplo
El siguiente programa TestThread muestra el uso de la interfaz Executor en un entorno basado en subprocesos.
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
Executor executor = Executors.newCachedThreadPool();
executor.execute(new Task());
ThreadPoolExecutor pool = (ThreadPoolExecutor)executor;
pool.shutdown();
}
static class Task implements Runnable {
public void run() {
try {
Long duration = (long) (Math.random() * 5);
System.out.println("Running Task!");
TimeUnit.SECONDS.sleep(duration);
System.out.println("Task Completed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Running Task!
Task Completed
Una interfaz java.util.concurrent.ExecutorService es una subinterfaz de la interfaz Executor y agrega características para administrar el ciclo de vida, tanto de las tareas individuales como del ejecutor mismo.
Métodos ExecutorService
No Señor. | Método y descripción |
---|---|
1 | boolean awaitTermination(long timeout, TimeUnit unit) Se bloquea hasta que todas las tareas hayan completado su ejecución después de una solicitud de apagado, o se agote el tiempo de espera o se interrumpa el hilo actual, lo que ocurra primero. |
2 | <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) Ejecuta las tareas asignadas, devolviendo una lista de futuros con su estado y resultados cuando todo se completa. |
3 | <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) Ejecuta las tareas asignadas y devuelve una lista de Futuros con su estado y resultados cuando todo se completa o el tiempo de espera expira, lo que ocurra primero. |
4 | <T> T invokeAny(Collection<? extends Callable<T>> tasks) Ejecuta las tareas dadas, devolviendo el resultado de una que se ha completado con éxito (es decir, sin lanzar una excepción), si alguna lo hace. |
5 | <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) Ejecuta las tareas dadas, devolviendo el resultado de una que se ha completado con éxito (es decir, sin lanzar una excepción), si alguna lo hace antes de que transcurra el tiempo de espera dado. |
6 | boolean isShutdown() Devuelve verdadero si este ejecutor ha sido cerrado. |
7 | boolean isTerminated() Devuelve verdadero si todas las tareas se completaron después del cierre. |
8 | void shutdown() Inicia un apagado ordenado en el que se ejecutan las tareas enviadas anteriormente, pero no se aceptarán nuevas tareas. |
9 | List<Runnable> shutdownNow() Intenta detener todas las tareas en ejecución activa, detiene el procesamiento de las tareas en espera y devuelve una lista de las tareas que estaban en espera de ejecución. |
10 | <T> Future<T> submit(Callable<T> task) Envía una tarea de devolución de valor para su ejecución y devuelve un Futuro que representa los resultados pendientes de la tarea. |
11 | Future<?> submit(Runnable task) Envía una tarea ejecutable para su ejecución y devuelve un futuro que representa esa tarea. |
12 | <T> Future<T> submit(Runnable task, T result) Envía una tarea ejecutable para su ejecución y devuelve un futuro que representa esa tarea. |
Ejemplo
El siguiente programa TestThread muestra el uso de la interfaz ExecutorService en un entorno basado en subprocesos.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
executor.submit(new Task());
System.out.println("Shutdown executor");
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
System.err.println("tasks interrupted");
} finally {
if (!executor.isTerminated()) {
System.err.println("cancel non-finished tasks");
}
executor.shutdownNow();
System.out.println("shutdown finished");
}
}
static class Task implements Runnable {
public void run() {
try {
Long duration = (long) (Math.random() * 20);
System.out.println("Running Task!");
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Shutdown executor
Running Task!
shutdown finished
cancel non-finished tasks
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:302)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:328)
at TestThread$Task.run(TestThread.java:39)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439) at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:662)
Una interfaz java.util.concurrent.ScheduledExecutorService es una subinterfaz de la interfaz ExecutorService, y admite la ejecución futura y / o periódica de tareas.
Métodos ScheduledExecutorService
No Señor. | Método y descripción |
---|---|
1 | <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) Crea y ejecuta un ScheduledFuture que se habilita después del retraso dado. |
2 | ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) Crea y ejecuta una acción de un solo paso que se habilita después del retraso dado. |
3 | ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) Crea y ejecuta una acción periódica que se habilita primero después del retraso inicial dado, y luego con el período dado; es decir, las ejecuciones comenzarán después de initialDelay, luego initialDelay + período, luego initialDelay + 2 * período, y así sucesivamente. |
4 | ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) Crea y ejecuta una acción periódica que se habilita primero después del retardo inicial dado, y luego con el retardo dado entre la terminación de una ejecución y el comienzo de la siguiente. |
Ejemplo
El siguiente programa TestThread muestra el uso de la interfaz ScheduledExecutorService en un entorno basado en subprocesos.
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
final ScheduledFuture<?> beepHandler =
scheduler.scheduleAtFixedRate(new BeepTask(), 2, 2, TimeUnit.SECONDS);
scheduler.schedule(new Runnable() {
@Override
public void run() {
beepHandler.cancel(true);
scheduler.shutdown();
}
}, 10, TimeUnit.SECONDS);
}
static class BeepTask implements Runnable {
public void run() {
System.out.println("beep");
}
}
}
Esto producirá el siguiente resultado.
Salida
beep
beep
beep
beep
Se puede obtener un grupo de subprocesos fijos llamando al método estático newFixedThreadPool () de la clase Executors.
Sintaxis
ExecutorService fixedPool = Executors.newFixedThreadPool(2);
dónde
Un máximo de 2 subprocesos estarán activos para procesar tareas.
Si se envían más de 2 subprocesos, se mantienen en una cola hasta que estén disponibles.
Se crea un nuevo hilo para tomar su lugar si un hilo termina debido a una falla durante el cierre de ejecución en el ejecutor que aún no se llama.
Cualquier hilo existe hasta que se cierra el grupo.
Ejemplo
El siguiente programa TestThread muestra el uso del método newFixedThreadPool en un entorno basado en subprocesos.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
// Cast the object to its class type
ThreadPoolExecutor pool = (ThreadPoolExecutor) executor;
//Stats before tasks execution
System.out.println("Largest executions: "
+ pool.getLargestPoolSize());
System.out.println("Maximum allowed threads: "
+ pool.getMaximumPoolSize());
System.out.println("Current threads in pool: "
+ pool.getPoolSize());
System.out.println("Currently executing threads: "
+ pool.getActiveCount());
System.out.println("Total number of threads(ever scheduled): "
+ pool.getTaskCount());
executor.submit(new Task());
executor.submit(new Task());
//Stats after tasks execution
System.out.println("Core threads: " + pool.getCorePoolSize());
System.out.println("Largest executions: "
+ pool.getLargestPoolSize());
System.out.println("Maximum allowed threads: "
+ pool.getMaximumPoolSize());
System.out.println("Current threads in pool: "
+ pool.getPoolSize());
System.out.println("Currently executing threads: "
+ pool.getActiveCount());
System.out.println("Total number of threads(ever scheduled): "
+ pool.getTaskCount());
executor.shutdown();
}
static class Task implements Runnable {
public void run() {
try {
Long duration = (long) (Math.random() * 5);
System.out.println("Running Task! Thread Name: " +
Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(duration);
System.out.println("Task Completed! Thread Name: " +
Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Largest executions: 0
Maximum allowed threads: 2
Current threads in pool: 0
Currently executing threads: 0
Total number of threads(ever scheduled): 0
Core threads: 2
Largest executions: 2
Maximum allowed threads: 2
Current threads in pool: 2
Currently executing threads: 1
Total number of threads(ever scheduled): 2
Running Task! Thread Name: pool-1-thread-1
Running Task! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-1
Se puede obtener un grupo de subprocesos en caché llamando al método estático newCachedThreadPool () de la clase Executors.
Sintaxis
ExecutorService executor = Executors.newCachedThreadPool();
dónde
El método newCachedThreadPool crea un ejecutor que tiene un grupo de subprocesos expandible.
Dicho ejecutor es adecuado para aplicaciones que inician muchas tareas de corta duración.
Ejemplo
El siguiente programa TestThread muestra el uso del método newCachedThreadPool en un entorno basado en subprocesos.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
ExecutorService executor = Executors.newCachedThreadPool();
// Cast the object to its class type
ThreadPoolExecutor pool = (ThreadPoolExecutor) executor;
//Stats before tasks execution
System.out.println("Largest executions: "
+ pool.getLargestPoolSize());
System.out.println("Maximum allowed threads: "
+ pool.getMaximumPoolSize());
System.out.println("Current threads in pool: "
+ pool.getPoolSize());
System.out.println("Currently executing threads: "
+ pool.getActiveCount());
System.out.println("Total number of threads(ever scheduled): "
+ pool.getTaskCount());
executor.submit(new Task());
executor.submit(new Task());
//Stats after tasks execution
System.out.println("Core threads: " + pool.getCorePoolSize());
System.out.println("Largest executions: "
+ pool.getLargestPoolSize());
System.out.println("Maximum allowed threads: "
+ pool.getMaximumPoolSize());
System.out.println("Current threads in pool: "
+ pool.getPoolSize());
System.out.println("Currently executing threads: "
+ pool.getActiveCount());
System.out.println("Total number of threads(ever scheduled): "
+ pool.getTaskCount());
executor.shutdown();
}
static class Task implements Runnable {
public void run() {
try {
Long duration = (long) (Math.random() * 5);
System.out.println("Running Task! Thread Name: " +
Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(duration);
System.out.println("Task Completed! Thread Name: " +
Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Largest executions: 0
Maximum allowed threads: 2147483647
Current threads in pool: 0
Currently executing threads: 0
Total number of threads(ever scheduled): 0
Core threads: 0
Largest executions: 2
Maximum allowed threads: 2147483647
Current threads in pool: 2
Currently executing threads: 2
Total number of threads(ever scheduled): 2
Running Task! Thread Name: pool-1-thread-1
Running Task! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-2
Task Completed! Thread Name: pool-1-thread-1
Se puede obtener un grupo de subprocesos programados llamando al método estático newScheduledThreadPool () de la clase Executors.
Sintaxis
ExecutorService executor = Executors.newScheduledThreadPool(1);
Ejemplo
El siguiente programa TestThread muestra el uso del método newScheduledThreadPool en un entorno basado en subprocesos.
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
final ScheduledFuture<?> beepHandler =
scheduler.scheduleAtFixedRate(new BeepTask(), 2, 2, TimeUnit.SECONDS);
scheduler.schedule(new Runnable() {
@Override
public void run() {
beepHandler.cancel(true);
scheduler.shutdown();
}
}, 10, TimeUnit.SECONDS);
}
static class BeepTask implements Runnable {
public void run() {
System.out.println("beep");
}
}
}
Esto producirá el siguiente resultado.
Salida
beep
beep
beep
beep
Se puede obtener un único grupo de subprocesos llamando al método estático newSingleThreadExecutor () de la clase Executors.
Sintaxis
ExecutorService executor = Executors.newSingleThreadExecutor();
Donde el método newSingleThreadExecutor crea un ejecutor que ejecuta una sola tarea a la vez.
Ejemplo
El siguiente programa TestThread muestra el uso del método newSingleThreadExecutor en un entorno basado en subprocesos.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
executor.submit(new Task());
System.out.println("Shutdown executor");
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
System.err.println("tasks interrupted");
} finally {
if (!executor.isTerminated()) {
System.err.println("cancel non-finished tasks");
}
executor.shutdownNow();
System.out.println("shutdown finished");
}
}
static class Task implements Runnable {
public void run() {
try {
Long duration = (long) (Math.random() * 20);
System.out.println("Running Task!");
TimeUnit.SECONDS.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Shutdown executor
Running Task!
shutdown finished
cancel non-finished tasks
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:302)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:328)
at TestThread$Task.run(TestThread.java:39) at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) at java.util.concurrent.FutureTask.run(FutureTask.java:138) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
at java.lang.Thread.run(Thread.java:662)
java.util.concurrent.ThreadPoolExecutor es un ExecutorService para ejecutar cada tarea enviada usando uno de posiblemente varios subprocesos agrupados, normalmente configurados usando los métodos de fábrica de Executors. También proporciona varios métodos de utilidad para verificar las estadísticas actuales de los hilos y controlarlos.
Métodos ThreadPoolExecutor
No Señor. | Método y descripción |
---|---|
1 | protected void afterExecute(Runnable r, Throwable t) Método invocado al finalizar la ejecución del Runnable dado. |
2 | void allowCoreThreadTimeOut(boolean value) Establece la política que rige si los subprocesos centrales pueden agotar el tiempo de espera y finalizar si no llegan tareas dentro del tiempo de mantenimiento, y se reemplazan si es necesario cuando llegan nuevas tareas. |
3 | boolean allowsCoreThreadTimeOut() Devuelve verdadero si este grupo permite que los subprocesos principales se agoten y terminen si no llegan tareas dentro del tiempo de keepAlive, y se reemplazan si es necesario cuando llegan nuevas tareas. |
4 | boolean awaitTermination(long timeout, TimeUnit unit) Se bloquea hasta que todas las tareas hayan completado su ejecución después de una solicitud de apagado, o se agote el tiempo de espera o se interrumpa el hilo actual, lo que ocurra primero. |
5 | protected void beforeExecute(Thread t, Runnable r) Método invocado antes de ejecutar el Runnable dado en el hilo dado. |
6 | void execute(Runnable command) Ejecuta la tarea dada en algún momento en el futuro. |
7 | protected void finalize() Invoca el cierre cuando ya no se hace referencia a este ejecutor y no tiene hilos. |
8 | int getActiveCount() Devuelve el número aproximado de subprocesos que están ejecutando tareas de forma activa. |
9 | long getCompletedTaskCount() Devuelve el número total aproximado de tareas que han completado su ejecución. |
10 | int getCorePoolSize() Devuelve el número principal de subprocesos. |
11 | long getKeepAliveTime(TimeUnit unit) Devuelve el tiempo de mantenimiento de subprocesos, que es la cantidad de tiempo que los subprocesos que superan el tamaño del grupo principal pueden permanecer inactivos antes de finalizar. |
12 | int getLargestPoolSize() Devuelve la mayor cantidad de subprocesos que alguna vez han estado simultáneamente en el grupo. |
13 | int getMaximumPoolSize() Devuelve el número máximo permitido de subprocesos. |
14 | int getPoolSize() Devuelve el número actual de subprocesos en el grupo. |
15 | BlockingQueue
Devuelve la cola de tareas utilizada por este ejecutor. |
15 | RejectedExecutionHandler getRejectedExecutionHandler() Devuelve el controlador actual para tareas no ejecutables. |
dieciséis | long getTaskCount() Devuelve el número total aproximado de tareas que se han programado para su ejecución. |
17 | ThreadFactory getThreadFactory() Devuelve la fábrica de hilos utilizada para crear nuevos hilos. |
18 | boolean isShutdown() Devuelve verdadero si este ejecutor ha sido cerrado. |
19 | boolean isTerminated() Devuelve verdadero si todas las tareas se completaron después del cierre. |
20 | boolean isTerminating() Devuelve verdadero si este ejecutor está en proceso de terminar después de shutdown () o shutdownNow () pero no ha terminado por completo. |
21 | int prestartAllCoreThreads() Inicia todos los subprocesos principales, lo que hace que esperen inactivos el trabajo. |
22 | boolean prestartCoreThread() Inicia un hilo central, lo que hace que espere inactivo el trabajo. |
23 | void purge() Intenta eliminar de la cola de trabajos todas las tareas futuras que se han cancelado. |
24 | boolean remove(Runnable task) Elimina esta tarea de la cola interna del ejecutor si está presente, lo que hace que no se ejecute si aún no se ha iniciado. |
25 | void setCorePoolSize(int corePoolSize) Establece el número principal de subprocesos. |
26 | void setKeepAliveTime(long time, TimeUnit unit) Establece el límite de tiempo durante el cual los hilos pueden permanecer inactivos antes de finalizar. |
27 | void setMaximumPoolSize(int maximumPoolSize) Establece el número máximo permitido de subprocesos. |
28 | void setRejectedExecutionHandler(RejectedExecutionHandler handler) Establece un nuevo controlador para tareas no ejecutables. |
29 | void setThreadFactory(ThreadFactory threadFactory) Establece la fábrica de hilos utilizada para crear nuevos hilos. |
30 | void shutdown() Inicia un apagado ordenado en el que se ejecutan las tareas enviadas anteriormente, pero no se aceptarán nuevas tareas. |
31 | List<Runnable> shutdownNow() Intenta detener todas las tareas en ejecución activa, detiene el procesamiento de las tareas en espera y devuelve una lista de las tareas que estaban en espera de ejecución. |
32 | protected void terminated() Método que se invoca cuando el Ejecutor ha terminado. |
33 | String toString() Devuelve una cadena que identifica este grupo, así como su estado, incluidas las indicaciones del estado de ejecución y los recuentos estimados de trabajadores y tareas. |
Ejemplo
El siguiente programa TestThread muestra el uso de la interfaz ThreadPoolExecutor en un entorno basado en subprocesos.
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
ThreadPoolExecutor executor = (ThreadPoolExecutor)Executors.newCachedThreadPool();
//Stats before tasks execution
System.out.println("Largest executions: "
+ executor.getLargestPoolSize());
System.out.println("Maximum allowed threads: "
+ executor.getMaximumPoolSize());
System.out.println("Current threads in pool: "
+ executor.getPoolSize());
System.out.println("Currently executing threads: "
+ executor.getActiveCount());
System.out.println("Total number of threads(ever scheduled): "
+ executor.getTaskCount());
executor.submit(new Task());
executor.submit(new Task());
//Stats after tasks execution
System.out.println("Core threads: " + executor.getCorePoolSize());
System.out.println("Largest executions: "
+ executor.getLargestPoolSize());
System.out.println("Maximum allowed threads: "
+ executor.getMaximumPoolSize());
System.out.println("Current threads in pool: "
+ executor.getPoolSize());
System.out.println("Currently executing threads: "
+ executor.getActiveCount());
System.out.println("Total number of threads(ever scheduled): "
+ executor.getTaskCount());
executor.shutdown();
}
static class Task implements Runnable {
public void run() {
try {
Long duration = (long) (Math.random() * 5);
System.out.println("Running Task! Thread Name: " +
Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(duration);
System.out.println("Task Completed! Thread Name: " +
Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Largest executions: 0
Maximum allowed threads: 2147483647
Current threads in pool: 0
Currently executing threads: 0
Total number of threads(ever scheduled): 0
Core threads: 0
Largest executions: 2
Maximum allowed threads: 2147483647
Current threads in pool: 2
Currently executing threads: 2
Total number of threads(ever scheduled): 2
Running Task! Thread Name: pool-1-thread-2
Running Task! Thread Name: pool-1-thread-1
Task Completed! Thread Name: pool-1-thread-1
Task Completed! Thread Name: pool-1-thread-2
java.util.concurrent.ScheduledThreadPoolExecutor es una subclase de ThreadPoolExecutor y, además, puede programar comandos para que se ejecuten después de un retraso determinado o para que se ejecuten periódicamente.
Métodos ScheduledThreadPoolExecutor
No Señor. | Método y descripción |
---|---|
1 | protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) Modifica o reemplaza la tarea utilizada para ejecutar un invocable. |
2 | protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) Modifica o reemplaza la tarea utilizada para ejecutar un ejecutable. |
3 | void execute(Runnable command) Ejecuta el comando sin demora requerida. |
4 | boolean getContinueExistingPeriodicTasksAfterShutdownPolicy() Obtiene la política sobre si continuar ejecutando tareas periódicas existentes incluso cuando este ejecutor se ha apagado. |
5 | boolean getExecuteExistingDelayedTasksAfterShutdownPolicy() Obtiene la política sobre si se deben ejecutar las tareas retrasadas existentes incluso cuando este ejecutor se ha apagado. |
6 | BlockingQueue<Runnable> getQueue() Devuelve la cola de tareas utilizada por este ejecutor. |
7 | boolean getRemoveOnCancelPolicy() Obtiene la política sobre si las tareas canceladas deben eliminarse inmediatamente de la cola de trabajos en el momento de la cancelación. |
8 | <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) Crea y ejecuta un ScheduledFuture que se habilita después del retraso dado. |
9 | ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) Crea y ejecuta una acción de un solo paso que se habilita después del retraso dado. |
10 | ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) Crea y ejecuta una acción periódica que se habilita primero después del retraso inicial dado, y luego con el período dado; es decir, las ejecuciones comenzarán después de initialDelay, luego initialDelay + período, luego initialDelay + 2 * período, y así sucesivamente. |
11 | ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) Crea y ejecuta una acción periódica que se habilita primero después del retardo inicial dado, y luego con el retardo dado entre la terminación de una ejecución y el comienzo de la siguiente. |
12 | void setContinueExistingPeriodicTasksAfterShutdownPolicy (boolean value) Establece la política sobre si continuar ejecutando tareas periódicas existentes incluso cuando este ejecutor se ha apagado. |
13 | void setExecuteExistingDelayedTasksAfterShutdownPolicy (boolean value) Establece la política sobre si ejecutar las tareas retrasadas existentes incluso cuando este ejecutor se ha apagado. |
14 | void setRemoveOnCancelPolicy(boolean value) Establece la política sobre si las tareas canceladas deben eliminarse inmediatamente de la cola de trabajos en el momento de la cancelación. |
15 | void shutdown() Inicia un apagado ordenado en el que se ejecutan las tareas enviadas anteriormente, pero no se aceptarán nuevas tareas. |
dieciséis | List<Runnable> shutdownNow() Intenta detener todas las tareas en ejecución activa, detiene el procesamiento de las tareas en espera y devuelve una lista de las tareas que estaban en espera de ejecución. |
17 | <T> Future<T> submit(Callable<T> task) Envía una tarea de devolución de valor para su ejecución y devuelve un Futuro que representa los resultados pendientes de la tarea. |
18 | Future<?> submit(Runnable task) Envía una tarea ejecutable para su ejecución y devuelve un futuro que representa esa tarea. |
19 | <T> Future<T> submit(Runnable task, T result) Envía una tarea ejecutable para su ejecución y devuelve un futuro que representa esa tarea. |
Ejemplo
El siguiente programa TestThread muestra el uso de la interfaz ScheduledThreadPoolExecutor en un entorno basado en subprocesos.
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
final ScheduledThreadPoolExecutor scheduler =
(ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(1);
final ScheduledFuture<?> beepHandler =
scheduler.scheduleAtFixedRate(new BeepTask(), 2, 2, TimeUnit.SECONDS);
scheduler.schedule(new Runnable() {
@Override
public void run() {
beepHandler.cancel(true);
scheduler.shutdown();
}
}, 10, TimeUnit.SECONDS);
}
static class BeepTask implements Runnable {
public void run() {
System.out.println("beep");
}
}
}
Esto producirá el siguiente resultado.
Salida
beep
beep
beep
beep
El objeto java.util.concurrent.Callable puede devolver el resultado calculado realizado por un hilo en contraste con la interfaz ejecutable que solo puede ejecutar el hilo. El objeto invocable devuelve el objeto Future que proporciona métodos para monitorear el progreso de una tarea que está siendo ejecutada por un hilo. El objeto Future se puede usar para verificar el estado de un Invocable y luego recuperar el resultado del Invocable una vez que el hilo está terminado. También proporciona la funcionalidad de tiempo de espera.
Sintaxis
//submit the callable using ThreadExecutor
//and get the result as a Future object
Future<Long> result10 = executor.submit(new FactorialService(10));
//get the result using get method of the Future object
//get method waits till the thread execution and then return the result of the execution.
Long factorial10 = result10.get();
Ejemplo
El siguiente programa TestThread muestra el uso de Futures y Callables en un entorno basado en subprocesos.
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException,
ExecutionException {
ExecutorService executor = Executors.newSingleThreadExecutor();
System.out.println("Factorial Service called for 10!");
Future<Long> result10 = executor.submit(new FactorialService(10));
System.out.println("Factorial Service called for 20!");
Future<Long> result20 = executor.submit(new FactorialService(20));
Long factorial10 = result10.get();
System.out.println("10! = " + factorial10);
Long factorial20 = result20.get();
System.out.println("20! = " + factorial20);
executor.shutdown();
}
static class FactorialService implements Callable<Long> {
private int number;
public FactorialService(int number) {
this.number = number;
}
@Override
public Long call() throws Exception {
return factorial();
}
private Long factorial() throws InterruptedException {
long result = 1;
while (number != 0) {
result = number * result;
number--;
Thread.sleep(100);
}
return result;
}
}
}
Esto producirá el siguiente resultado.
Salida
Factorial Service called for 10!
Factorial Service called for 20!
10! = 3628800
20! = 2432902008176640000
El framework fork-join permite dividir una determinada tarea en varios trabajadores y luego esperar el resultado para combinarlos. Aprovecha en gran medida la capacidad de la máquina multiprocesador. A continuación se muestran los conceptos y objetos básicos que se utilizan en el marco de unión de bifurcación.
Tenedor
Fork es un proceso en el que una tarea se divide en subtareas más pequeñas e independientes que se pueden ejecutar al mismo tiempo.
Sintaxis
Sum left = new Sum(array, low, mid);
left.fork();
Aquí Sum es una subclase de RecursiveTask y left.fork () divide la tarea en subtareas.
Unirse
Join es un proceso en el que una tarea une todos los resultados de las subtareas una vez que las subtareas han terminado de ejecutarse, de lo contrario sigue esperando.
Sintaxis
left.join();
Aquí a la izquierda hay un objeto de la clase Sum.
HorquillaUnirsePiscina
es un grupo de subprocesos especial diseñado para trabajar con la división de tareas de bifurcación y unión.
Sintaxis
ForkJoinPool forkJoinPool = new ForkJoinPool(4);
Aquí un nuevo ForkJoinPool con un nivel de paralelismo de 4 CPU.
RecursiveAction
RecursiveAction representa una tarea que no devuelve ningún valor.
Sintaxis
class Writer extends RecursiveAction {
@Override
protected void compute() { }
}
RecursiveTask
RecursiveTask representa una tarea que devuelve un valor.
Sintaxis
class Sum extends RecursiveTask<Long> {
@Override
protected Long compute() { return null; }
}
Ejemplo
El siguiente programa TestThread muestra el uso del marco Fork-Join en un entorno basado en subprocesos.
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException,
ExecutionException {
int nThreads = Runtime.getRuntime().availableProcessors();
System.out.println(nThreads);
int[] numbers = new int[1000];
for(int i = 0; i < numbers.length; i++) {
numbers[i] = i;
}
ForkJoinPool forkJoinPool = new ForkJoinPool(nThreads);
Long result = forkJoinPool.invoke(new Sum(numbers,0,numbers.length));
System.out.println(result);
}
static class Sum extends RecursiveTask<Long> {
int low;
int high;
int[] array;
Sum(int[] array, int low, int high) {
this.array = array;
this.low = low;
this.high = high;
}
protected Long compute() {
if(high - low <= 10) {
long sum = 0;
for(int i = low; i < high; ++i)
sum += array[i];
return sum;
} else {
int mid = low + (high - low) / 2;
Sum left = new Sum(array, low, mid);
Sum right = new Sum(array, mid, high);
left.fork();
long rightResult = right.compute();
long leftResult = left.join();
return leftResult + rightResult;
}
}
}
}
Esto producirá el siguiente resultado.
Salida
32
499500
Una interfaz java.util.concurrent.BlockingQueue es una subinterfaz de la interfaz Queue y, además, admite operaciones como esperar a que la cola no esté vacía antes de recuperar un elemento y esperar a que haya espacio disponible en la cola antes de almacenar un elemento. .
Métodos BlockingQueue
No Señor. | Método y descripción |
---|---|
1 | boolean add(E e) Inserta el elemento especificado en esta cola si es posible hacerlo inmediatamente sin violar las restricciones de capacidad, devolviendo verdadero en caso de éxito y lanzando una IllegalStateException si no hay espacio disponible actualmente. |
2 | boolean contains(Object o) Devuelve verdadero si esta cola contiene el elemento especificado. |
3 | int drainTo(Collection<? super E> c) Elimina todos los elementos disponibles de esta cola y los agrega a la colección dada. |
4 | int drainTo(Collection<? super E> c, int maxElements) Elimina como máximo el número dado de elementos disponibles de esta cola y los agrega a la colección dada. |
5 | boolean offer(E e) Inserta el elemento especificado en esta cola si es posible hacerlo inmediatamente sin violar las restricciones de capacidad, devolviendo verdadero en caso de éxito y falso si no hay espacio disponible actualmente. |
6 | boolean offer(E e, long timeout, TimeUnit unit) Inserta el elemento especificado en esta cola, esperando hasta el tiempo de espera especificado si es necesario para que haya espacio disponible. |
7 | E poll(long timeout, TimeUnit unit) Recupera y elimina el encabezado de esta cola, esperando hasta el tiempo de espera especificado si es necesario para que un elemento esté disponible. |
8 | void put(E e) Inserta el elemento especificado en esta cola, esperando si es necesario hasta que haya espacio disponible. |
9 | int remainingCapacity() Devuelve el número de elementos adicionales que esta cola puede aceptar idealmente (en ausencia de restricciones de memoria o recursos) sin bloquear, o Integer.MAX_VALUE si no hay un límite intrínseco. |
10 | boolean remove(Object o) Elimina una única instancia del elemento especificado de esta cola, si está presente. |
11 | E take() Recupera y elimina el encabezado de esta cola, esperando si es necesario hasta que un elemento esté disponible. |
Ejemplo
El siguiente programa TestThread muestra el uso de la interfaz BlockingQueue en un entorno basado en subprocesos.
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class TestThread {
public static void main(final String[] arguments) throws InterruptedException {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
new Thread(producer).start();
new Thread(consumer).start();
Thread.sleep(4000);
}
static class Producer implements Runnable {
private BlockingQueue<Integer> queue;
public Producer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
Random random = new Random();
try {
int result = random.nextInt(100);
Thread.sleep(1000);
queue.put(result);
System.out.println("Added: " + result);
result = random.nextInt(100);
Thread.sleep(1000);
queue.put(result);
System.out.println("Added: " + result);
result = random.nextInt(100);
Thread.sleep(1000);
queue.put(result);
System.out.println("Added: " + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Consumer implements Runnable {
private BlockingQueue<Integer> queue;
public Consumer(BlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
try {
System.out.println("Removed: " + queue.take());
System.out.println("Removed: " + queue.take());
System.out.println("Removed: " + queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Esto producirá el siguiente resultado.
Salida
Added: 52
Removed: 52
Added: 70
Removed: 70
Added: 27
Removed: 27
Una interfaz java.util.concurrent.ConcurrentMap es una subinterfaz de la interfaz Map, admite operaciones atómicas en la variable de mapa subyacente. Tiene métodos get y set que funcionan como lectura y escritura en variables volátiles. Es decir, un conjunto tiene una relación de suceder antes con cualquier obtención posterior de la misma variable. Esta interfaz garantiza la seguridad de los hilos y las garantías de atomicidad.
Métodos ConcurrentMap
No Señor. | Método y descripción |
---|---|
1 | default V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) Intenta calcular un mapeo para la clave especificada y su valor mapeado actual (o nulo si no hay mapeo actual). |
2 | default V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) Si la clave especificada aún no está asociada con un valor (o está asignada a nulo), intenta calcular su valor utilizando la función de asignación dada y la ingresa en este mapa a menos que sea nula. |
3 | default V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction) Si el valor de la clave especificada está presente y no es nulo, intenta calcular una nueva asignación dada la clave y su valor asignado actual. |
4 | default void forEach(BiConsumer<? super K,? super V> action) Realiza la acción dada para cada entrada en este mapa hasta que se hayan procesado todas las entradas o la acción arroje una excepción. |
5 | default V getOrDefault(Object key, V defaultValue) Devuelve el valor al que se asigna la clave especificada, o defaultValue si este mapa no contiene ninguna asignación para la clave. |
6 | default V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction) Si la clave especificada aún no está asociada con un valor o está asociada con nulo, la asocia con el valor no nulo dado. |
7 | V putIfAbsent(K key, V value) Si la clave especificada aún no está asociada con un valor, asóciela con el valor dado. |
8 | boolean remove(Object key, Object value) Elimina la entrada de una clave solo si está asignada actualmente a un valor determinado. |
9 | V replace(K key, V value) Reemplaza la entrada de una clave solo si actualmente está asignada a algún valor. |
10 | boolean replace(K key, V oldValue, V newValue) Reemplaza la entrada de una clave solo si actualmente está asignada a un valor dado. |
11 | default void replaceAll(BiFunction<? super K,? super V,? extends V> function) Reemplaza el valor de cada entrada con el resultado de invocar la función dada en esa entrada hasta que todas las entradas hayan sido procesadas o la función arroje una excepción. |
Ejemplo
El siguiente programa TestThread muestra el uso de ConcurrentMap vs HashMap.
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class TestThread {
public static void main(final String[] arguments) {
Map<String,String> map = new ConcurrentHashMap<String, String>();
map.put("1", "One");
map.put("2", "Two");
map.put("3", "Three");
map.put("5", "Five");
map.put("6", "Six");
System.out.println("Initial ConcurrentHashMap: " + map);
Iterator<String> iterator = map.keySet().iterator();
try {
while(iterator.hasNext()) {
String key = iterator.next();
if(key.equals("3")) {
map.put("4", "Four");
}
}
} catch(ConcurrentModificationException cme) {
cme.printStackTrace();
}
System.out.println("ConcurrentHashMap after modification: " + map);
map = new HashMap<String, String>();
map.put("1", "One");
map.put("2", "Two");
map.put("3", "Three");
map.put("5", "Five");
map.put("6", "Six");
System.out.println("Initial HashMap: " + map);
iterator = map.keySet().iterator();
try {
while(iterator.hasNext()) {
String key = iterator.next();
if(key.equals("3")) {
map.put("4", "Four");
}
}
System.out.println("HashMap after modification: " + map);
} catch(ConcurrentModificationException cme) {
cme.printStackTrace();
}
}
}
Esto producirá el siguiente resultado.
Salida
Initial ConcurrentHashMap: {1 = One, 2 = Two, 3 = Three, 5 = Five, 6 = Six}
ConcurrentHashMap after modification: {1 = One, 2 = Two, 3 = Three, 4 = Four, 5 = Five, 6 = Six}
Initial HashMap: {1 = One, 2 = Two, 3 = Three, 5 = Five, 6 = Six}
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(Unknown Source)
at java.util.HashMap$KeyIterator.next(Unknown Source)
at TestThread.main(TestThread.java:48)
Una interfaz java.util.concurrent.ConcurrentNavigableMap es una subinterfaz de la interfaz ConcurrentMap y admite operaciones NavigableMap, y de forma recursiva para sus submapas navegables y coincidencias aproximadas.
Métodos ConcurrentMap
No Señor. | Método y descripción |
---|---|
1 | NavigableSet<K> descendingKeySet() Devuelve una vista NavigableSet en orden inverso de las claves contenidas en este mapa. |
2 | ConcurrentNavigableMap<K,V> descendingMap() Devuelve una vista en orden inverso de las asignaciones contenidas en este mapa. |
3 | ConcurrentNavigableMap<K,V> headMap(K toKey) Devuelve una vista de la parte de este mapa cuyas claves son estrictamente menores que toKey. |
4 | ConcurrentNavigableMap<K,V> headMap(K toKey, boolean inclusive) Devuelve una vista de la parte de este mapa cuyas claves son menores (o iguales a, si inclusivo es verdadero) toKey. |
5 | NavigableSet<K> keySet() Devuelve una vista NavigableSet de las claves contenidas en este mapa. |
6 | NavigableSet<K> navigableKeySet() Devuelve una vista NavigableSet de las claves contenidas en este mapa. |
7 | ConcurrentNavigableMap<K,V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) Devuelve una vista de la parte de este mapa cuyas claves van de fromKey a toKey. |
8 | ConcurrentNavigableMap<K,V> subMap(K fromKey, K toKey) Devuelve una vista de la parte de este mapa cuyas claves van de fromKey, inclusive, a toKey, exclusivo. |
9 | ConcurrentNavigableMap<K,V> tailMap(K fromKey) Devuelve una vista de la parte de este mapa cuyas claves son mayores o iguales que fromKey. |
10 | ConcurrentNavigableMap<K,V> tailMap(K fromKey, boolean inclusive) Devuelve una vista de la parte de este mapa cuyas claves son mayores que (o iguales a, si inclusivo es verdadero) fromKey. |
Ejemplo
El siguiente programa TestThread muestra el uso de ConcurrentNavigableMap.
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
public class TestThread {
public static void main(final String[] arguments) {
ConcurrentNavigableMap<String,String> map =
new ConcurrentSkipListMap<String, String>();
map.put("1", "One");
map.put("2", "Two");
map.put("3", "Three");
map.put("5", "Five");
map.put("6", "Six");
System.out.println("Initial ConcurrentHashMap: "+map);
System.out.println("HeadMap(\"2\") of ConcurrentHashMap: "+map.headMap("2"));
System.out.println("TailMap(\"2\") of ConcurrentHashMap: "+map.tailMap("2"));
System.out.println(
"SubMap(\"2\", \"4\") of ConcurrentHashMap: "+map.subMap("2","4"));
}
}
Esto producirá el siguiente resultado.
Salida
Initial ConcurrentHashMap: {1 = One, 2 = Two, 3 = Three, 5 = Five, 6 = Six}
HeadMap("2") of ConcurrentHashMap: {1 = One}
TailMap("2") of ConcurrentHashMap: {2 = Two, 3 = Three, 5 = Five, 6 = Six}
SubMap("2", "4") of ConcurrentHashMap: {2 = Two, 3 = Three}