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

  1. Introducció a java.util.concurrent
  2. Executors
  3. Col·leccions Concurrent
  4. Locks
  5. Barriers i Latches
  6. Exemples Pràctics
  7. Exercicis

  1. Introducció a 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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

Mòdul 2: Flux de Control

Mòdul 3: Programació Orientada a Objectes

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

Mòdul 8: Multithreading i Concurrència

Mòdul 9: Xarxes

Mòdul 10: Temes Avançats

Mòdul 11: Frameworks i Llibreries de Java

Mòdul 12: Construcció d'Aplicacions del Món Real

© Copyright 2024. Tots els drets reservats