En aquest tema, explorarem les utilitats de concurrència proporcionades per la biblioteca java.util.concurrent
de Java. Aquestes utilitats faciliten la gestió de fils i la sincronització en aplicacions multithreading, millorant la seguretat i l'eficiència del codi.
Continguts
- Introducció a
java.util.concurrent
- Executors
- Col·leccions Concurrent
- Locks
- Barriers i Latches
- Exemples Pràctics
- Exercicis
- Introducció a
java.util.concurrent
java.util.concurrent
La biblioteca java.util.concurrent
proporciona una sèrie de classes i interfícies per gestionar la concurrència de manera més eficient i segura. Algunes de les funcionalitats clau inclouen:
- Executors: Per gestionar grups de fils.
- Col·leccions Concurrent: Per a col·leccions segures en entorns multithreading.
- Locks: Per a mecanismes de sincronització més flexibles que els blocs sincronitzats.
- Barriers i Latches: Per a la coordinació de fils.
- Executors
Els executors són una manera d'abstraure la creació i gestió de fils. La interfície ExecutorService
és la més utilitzada.
Exemple de codi:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { executor.submit(new Task(i)); } executor.shutdown(); } } class Task implements Runnable { private int taskId; public Task(int id) { this.taskId = id; } @Override public void run() { System.out.println("Task " + taskId + " is running."); } }
Explicació:
- ExecutorService: Crea un grup de fils amb una mida fixa.
- submit: Envia tasques al grup de fils per a la seva execució.
- shutdown: Tanca l'executor després de completar totes les tasques.
- Col·leccions Concurrent
Les col·leccions concurrent proporcionen versions segures de col·leccions comunes com ara List
, Map
, Queue
, etc.
Exemple de codi:
import java.util.concurrent.ConcurrentHashMap; public class ConcurrentMapExample { public static void main(String[] args) { ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("One", 1); map.put("Two", 2); System.out.println(map.get("One")); } }
Explicació:
- ConcurrentHashMap: Una implementació segura de
HashMap
per a entorns multithreading.
- Locks
Els locks proporcionen un control més flexible sobre la sincronització que els blocs sincronitzats.
Exemple de codi:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockExample { private final Lock lock = new ReentrantLock(); public void performTask() { lock.lock(); try { // Codi crític System.out.println("Task is being performed."); } finally { lock.unlock(); } } public static void main(String[] args) { LockExample example = new LockExample(); example.performTask(); } }
Explicació:
- ReentrantLock: Un tipus de lock que permet a un fil adquirir el lock diverses vegades.
- lock: Adquireix el lock.
- unlock: Allibera el lock.
- Barriers i Latches
Les barriers i latches són mecanismes per coordinar la finalització de múltiples fils.
Exemple de codi amb CountDownLatch
:
import java.util.concurrent.CountDownLatch; public class LatchExample { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(3); for (int i = 0; i < 3; i++) { new Thread(new Worker(latch)).start(); } latch.await(); System.out.println("All tasks are completed."); } } class Worker implements Runnable { private final CountDownLatch latch; public Worker(CountDownLatch latch) { this.latch = latch; } @Override public void run() { System.out.println("Task is running."); latch.countDown(); } }
Explicació:
- CountDownLatch: Permet que un o més fils esperin fins que un conjunt d'operacions en altres fils es completin.
- countDown: Decrementa el comptador del latch.
- await: Espera fins que el comptador arribi a zero.
- Exemples Pràctics
ExecutorService amb Callable:
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 CallableExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); Future<Integer> future = executor.submit(new Task()); try { Integer result = future.get(); System.out.println("Result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } } class Task implements Callable<Integer> { @Override public Integer call() { return 123; } }
Explicació:
- Callable: Similar a
Runnable
, però pot retornar un resultat i llençar excepcions. - Future: Representa el resultat d'una operació asincrònica.
- Exercicis
Exercici 1: Utilitzar ExecutorService
Crea un programa que utilitzi ExecutorService
per executar 10 tasques en paral·lel. Cada tasca ha de imprimir el seu identificador i dormir durant 1 segon.
Solució:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExecutorExercise { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { final int id = i; executor.submit(() -> { System.out.println("Task " + id + " is running."); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); } executor.shutdown(); } }
Exercici 2: Utilitzar CountDownLatch
Crea un programa que utilitzi CountDownLatch
per esperar que 5 fils completin la seva tasca abans de continuar.
Solució:
import java.util.concurrent.CountDownLatch; public class LatchExercise { public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(5); for (int i = 0; i < 5; i++) { new Thread(() -> { System.out.println("Task is running."); latch.countDown(); }).start(); } latch.await(); System.out.println("All tasks are completed."); } }
Conclusió
En aquest tema, hem explorat les utilitats de concurrència proporcionades per la biblioteca java.util.concurrent
. Hem après a utilitzar ExecutorService
per gestionar fils, col·leccions concurrent per a la seguretat en entorns multithreading, locks per a la sincronització flexible, i mecanismes de coordinació com CountDownLatch
. Aquests conceptes són fonamentals per escriure aplicacions multithreading eficients i segures en Java.
Curs de Programació en Java
Mòdul 1: Introducció a Java
- Introducció a Java
- Configuració de l'Entorn de Desenvolupament
- Sintaxi i Estructura Bàsica
- Variables i Tipus de Dades
- Operadors
Mòdul 2: Flux de Control
Mòdul 3: Programació Orientada a Objectes
- Introducció a la POO
- Classes i Objectes
- Mètodes
- Constructors
- Herència
- Polimorfisme
- Encapsulació
- Abstracció
Mòdul 4: Programació Orientada a Objectes Avançada
Mòdul 5: Estructures de Dades i Col·leccions
Mòdul 6: Gestió d'Excepcions
Mòdul 7: Entrada/Sortida de Fitxers
- Lectura de Fitxers
- Escriptura de Fitxers
- Fluxos de Fitxers
- BufferedReader i BufferedWriter
- Serialització
Mòdul 8: Multithreading i Concurrència
- Introducció al Multithreading
- Creació de Fils
- Cicle de Vida dels Fils
- Sincronització
- Utilitats de Concurrència
Mòdul 9: Xarxes
- Introducció a les Xarxes
- Sockets
- ServerSocket
- DatagramSocket i DatagramPacket
- URL i HttpURLConnection