La sincronització és un concepte clau en la programació multithreading que permet controlar l'accés concurrent a recursos compartits. Sense una sincronització adequada, els fils poden interferir entre ells, causant errors com condicions de carrera, inconsistències de dades i altres problemes difícils de diagnosticar.
Conceptes Clau
- Condicions de Carrera: Situació en què dos o més fils accedeixen a dades compartides i intenten canviar-les simultàniament.
- Bloqueig (Lock): Mecanisme que permet a un fil obtenir l'accés exclusiu a un recurs compartit.
- Monitor: Estructura que permet la sincronització de fils mitjançant l'ús de bloquejos i condicions.
Paraula Clau synchronized
En Java, la paraula clau synchronized
s'utilitza per controlar l'accés a blocs de codi o mètodes que han de ser executats per un sol fil a la vegada.
Sincronització de Mètodes
Quan un mètode està marcat com synchronized
, el fil que l'executa obté el bloqueig del monitor de l'objecte abans d'executar el mètode. Altres fils han d'esperar fins que el bloqueig sigui alliberat.
public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } }
Sincronització de Blocs de Codi
És possible sincronitzar només una part d'un mètode utilitzant blocs de codi sincronitzats. Això permet una major flexibilitat i pot millorar el rendiment.
public class Counter { private int count = 0; public void increment() { synchronized (this) { count++; } } public int getCount() { synchronized (this) { return count; } } }
Sincronització Estàtica
Els mètodes estàtics també poden ser sincronitzats. En aquest cas, el bloqueig es fa sobre la classe en lloc de l'objecte.
public class StaticCounter { private static int count = 0; public static synchronized void increment() { count++; } public static synchronized int getCount() { return count; } }
Exemples Pràctics
Exemple 1: Condició de Carrera
Sense sincronització, els fils poden interferir entre ells, causant resultats inesperats.
public class RaceConditionExample { private int count = 0; public void increment() { count++; } public static void main(String[] args) { RaceConditionExample example = new RaceConditionExample(); Runnable task = () -> { for (int i = 0; i < 1000; i++) { example.increment(); } }; Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final count: " + example.count); } }
Exemple 2: Solució amb Sincronització
Afegint sincronització, podem evitar la condició de carrera.
public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; } public static void main(String[] args) { SynchronizedExample example = new SynchronizedExample(); Runnable task = () -> { for (int i = 0; i < 1000; i++) { example.increment(); } }; Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final count: " + example.count); } }
Exercicis Pràctics
Exercici 1: Sincronització de Mètodes
Crea una classe BankAccount
amb els següents mètodes sincronitzats:
deposit(int amount)
: Afegeix una quantitat al saldo.withdraw(int amount)
: Resta una quantitat del saldo si hi ha prou fons.getBalance()
: Retorna el saldo actual.
Solució
public class BankAccount { private int balance = 0; public synchronized void deposit(int amount) { balance += amount; } public synchronized void withdraw(int amount) { if (balance >= amount) { balance -= amount; } } public synchronized int getBalance() { return balance; } public static void main(String[] args) { BankAccount account = new BankAccount(); Runnable depositTask = () -> { for (int i = 0; i < 1000; i++) { account.deposit(1); } }; Runnable withdrawTask = () -> { for (int i = 0; i < 1000; i++) { account.withdraw(1); } }; Thread thread1 = new Thread(depositTask); Thread thread2 = new Thread(withdrawTask); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final balance: " + account.getBalance()); } }
Resum
En aquesta secció, hem après sobre la importància de la sincronització en la programació multithreading per evitar condicions de carrera i altres problemes de concurrència. Hem vist com utilitzar la paraula clau synchronized
per sincronitzar mètodes i blocs de codi, així com exemples pràctics per il·lustrar aquests conceptes. La sincronització és essencial per garantir que els recursos compartits siguin accessibles de manera segura per múltiples fils.
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