Introducción
Las transmisiones son algo interesante. En revisiones anteriores, analizamos algunas de las herramientas disponibles para implementar subprocesos múltiples. Veamos qué más cosas interesantes podemos hacer. A estas alturas sabemos mucho. Por ejemplo, de "
No se puede estropear Java con un subproceso: Parte I - Subprocesos ", sabemos que un subproceso es un subproceso. Sabemos que un hilo está realizando alguna tarea. Si queremos que nuestra tarea pueda ejecutarse (
run
), debemos especificar que el hilo sea un determinado
Runnable
.
Para recordar, podemos usar
el compilador en línea Java de Tutorialspoint :
public static void main(String []args){
Runnable task = () -> {
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
};
Thread thread = new Thread(task);
thread.start();
}
También sabemos que tenemos el concepto de candado. Leemos sobre esto en "
No se puede estropear Java con un hilo: Parte II - Sincronización ". Un hilo puede ocupar un candado y luego otro hilo que intente ocupar el candado se verá obligado a esperar a que el candado se libere:
import java.util.concurrent.locks.*;
public class HelloWorld{
public static void main(String []args){
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
lock.unlock();
};
Thread thread = new Thread(task);
thread.start();
}
}
Creo que es hora de hablar sobre qué más podemos hacer que sea interesante.
Semáforos
El medio más sencillo para controlar cuántos subprocesos pueden funcionar simultáneamente es un semáforo. Como en el ferrocarril. La luz verde está encendida: puedes hacerlo. La luz roja está encendida: estamos esperando. ¿Qué esperamos de un semáforo? Permisos. Permiso en inglés - permiso. Para obtener el permiso es necesario obtenerlo, que en inglés se adquirirá. Y cuando ya no sea necesario el permiso, debemos regalarlo, es decir, liberarlo o deshacernos de él, que en inglés será liberar. Vamos a ver cómo funciona. Necesitaremos importar la clase
java.util.concurrent.Semaphore
. Ejemplo:
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println("Finished");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
Thread.sleep(5000);
semaphore.release(1);
}
Como podemos ver, después de memorizar palabras en inglés, entendemos cómo funciona el semáforo. Curiosamente, la condición principal es que la “cuenta” del semáforo debe tener un número positivo de permisos. Por lo tanto, puedes iniciarlo con un menos. Y puedes solicitar (adquirir) más de 1.
cuenta atrás
El siguiente mecanismo es
CountDownLatch
. CountDown en inglés es una cuenta regresiva y Latch es un perno o pestillo. Es decir, si lo traducimos, entonces es un pestillo con cuenta regresiva. Aquí necesitamos la importación apropiada de la clase
java.util.concurrent.CountDownLatch
. Es como una carrera o carrera donde todos se reúnen en la línea de salida y cuando todos están listos, se da permiso y todos comienzan al mismo tiempo. Ejemplo:
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
Runnable task = () -> {
try {
countDownLatch.countDown();
System.out.println("Countdown: " + countDownLatch.getCount());
countDownLatch.await();
System.out.println("Finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
esperar en inglés - esperar. Es decir, hablamos primero
countDown
. Como dice Google Translator, la cuenta regresiva es "un acto de contar números en orden inverso hasta cero", es decir, realizar una acción de cuenta regresiva, cuyo propósito es contar hasta cero. Y luego decimos
await
, es decir, espere hasta que el valor del contador llegue a cero. Es interesante que dicho mostrador sea desechable. Como se dice en JavaDoc: "Cuando los subprocesos deben contar repetidamente de esta manera, use CyclicBarrier", es decir, si necesita un recuento reutilizable, debe usar otra opción, que se llama
CyclicBarrier
.
Barrera cíclica
Como sugiere el nombre,
CyclicBarrier
es una barrera cíclica. Necesitaremos importar la clase
java.util.concurrent.CyclicBarrier
. Veamos un ejemplo:
public static void main(String[] args) throws InterruptedException {
Runnable action = () -> System.out.println("На старт!");
CyclicBarrier berrier = new CyclicBarrier(3, action);
Runnable task = () -> {
try {
berrier.await();
System.out.println("Finished");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("Limit: " + berrier.getParties());
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
Como puedes ver, el hilo se está ejecutando
await
, es decir, esperando. En este caso, el valor de la barrera disminuye. La barrera se considera rota (
berrier.isBroken()
) cuando la cuenta atrás llega a cero. Para restablecer la barrera, es necesario llamar a
berrier.reset()
, que faltaba en
CountDownLatch
.
Intercambiador
El siguiente remedio es
Exchanger
. Exchange del inglés se traduce como intercambio o intercambio. A
Exchanger
es un intercambiador, es decir, algo a través de lo cual intercambian. Veamos un ejemplo sencillo:
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Runnable task = () -> {
try {
Thread thread = Thread.currentThread();
String withThreadName = exchanger.exchange(thread.getName());
System.out.println(thread.getName() + " обменялся с " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
Aquí lanzamos dos hilos. Cada uno de ellos ejecuta el método de intercambio y espera a que otro hilo también ejecute el método de intercambio. Así, los hilos intercambiarán los argumentos pasados entre ellos. Cosa interesante. ¿No te recuerda a nada? Y recuerda
SynchronousQueue
lo que se encuentra en el corazón de
cachedThreadPool
'a. Para mayor claridad, aquí hay un ejemplo:
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Runnable task = () -> {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
queue.put("Message");
}
El ejemplo muestra que al iniciar un nuevo hilo, este hilo entrará en modo de espera, porque la cola estará vacía. Y luego
main
el hilo pondrá en cola el texto "Mensaje". Al mismo tiempo, se detendrá durante el tiempo requerido hasta que reciba este elemento de texto de la cola. Sobre este tema también puedes leer "
SynchronousQueue Vs Exchanger ".
fáser
Y finalmente, lo más dulce
Phaser
... Necesitaremos importar la clase
java.util.concurrent.Phaser
. Veamos un ejemplo sencillo:
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser();
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
phaser.register();
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance();
System.out.println(name + " after passing barrier");
}).start();
}
El ejemplo muestra que la barrera, al utilizar
Phaser
'a, se rompe cuando el número de registros coincide con el número de llegadas a la barrera. Puede obtener más información
Phaser
en el artículo del centro "
Nuevo sincronizador Phaser ".
Resultados
Como puede ver en los ejemplos, existen diferentes formas de sincronizar subprocesos. Anteriormente intenté recordar algo sobre subprocesos múltiples, espero que las partes anteriores hayan sido útiles. Dicen que el camino hacia el subproceso múltiple comienza con el libro "Java Concurrency in Practice". Aunque salió a la luz en 2006, la gente responde que el libro es bastante fundamental y todavía tiene gran impacto. Por ejemplo, puede leer las discusiones aquí: "¿
Sigue siendo válida la concurrencia de Java en la práctica? ". También es útil leer los enlaces de la discusión. Por ejemplo, hay un enlace al libro "
The Well-Grounded Java Developer ", donde vale la pena prestar atención al "
Capítulo 4. Concurrencia moderna ". Hay otra reseña completa sobre el mismo tema: "¿
La cocurrencia de Java en la práctica sigue siendo relevante en la era de Java 8 ?". También tiene consejos sobre qué más debería leer para comprender realmente el tema. Después de eso, puede echar un vistazo más de cerca a un libro tan maravilloso como "
Pruebas de práctica del programador OCA OCP JavaSE 8 ". Nos interesa la segunda parte, es decir, OCP. Y hay pruebas en "∫". Este libro contiene preguntas y respuestas con explicaciones. Por ejemplo:
Muchos podrán empezar a decir que esto es sólo otra memorización de métodos. Por un lado, sí. Por otro lado, esta pregunta se puede responder recordando que
ExecutorService
se trata de una especie de “actualización”
Executor
. Y
Executor
su objetivo es simplemente ocultar el método de creación de hilos, pero no la forma principal de ejecutarlos, es decir, ejecutar en un hilo nuevo
Runnable
. Por lo tanto
execute(Callable)
no, porque simplemente agregaron métodos que
ExecutorService
pueden regresar . Como puedes ver, podemos memorizar una lista de métodos, pero es mucho más fácil adivinar si conocemos la naturaleza de las clases mismas. Bueno, algunos materiales adicionales sobre el tema:
Executor
submit
Future
#viacheslav
GO TO FULL VERSION