Sobes.tech
Middle

Perché si verificano le race condition e quali metodi esistono per combatterle?

sobes.tech AI

Risposta dell'AI

Le condizioni di gara (race conditions) si verificano quando più thread accedono contemporaneamente a dati condivisi modificabili, e il risultato dipende dall'ordine di esecuzione. Questo accade perché il sistema operativo può cambiare thread in qualsiasi momento, interrompendo la loro esecuzione.

Modi per combattere:

  • Sincronizzazione: Uso di primitive di sincronizzazione per garantire accesso esclusivo alle risorse condivise.

    • Parola chiave synchronized: Applicata a metodi o blocchi di codice. Blocca l'accesso a un oggetto o classe per altri thread fino a quando il thread corrente non termina l'esecuzione del blocco/metodo sincronizzato.

      // Sincronizzazione del metodo
      public synchronized void updateData(int newValue) {
          // ... aggiornamento dati condivisi
      }
      
      // Sincronizzazione del blocco di codice
      public void processData() {
          synchronized (this) { // O altro oggetto monitor
              // ... accesso ai dati condivisi
          }
      }
      
    • Lock (Lock): Offrono un controllo più flessibile sulla sincronizzazione rispetto a synchronized.

      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      private final Lock dataLock = new ReentrantLock();
      
      public void safeUpdateData(int newValue) {
          dataLock.lock(); // Acquisizione del lock
          try {
              // ... aggiornamento dati condivisi
          } finally {
              dataLock.unlock(); // Rilascio del lock
          }
      }
      
  • Variabili atomiche (Atomic...): Forniscono operazioni thread-safe su tipi primitivi senza blocco esplicito. Sono usate per operazioni semplici (incremento, confronto e scambio).

    import java.util.concurrent.atomic.AtomicInteger;
    
    private final AtomicInteger counter = new AtomicInteger(0);
    
    public void incrementCounter() {
        counter.incrementAndGet(); // Incremento atomico
    }
    
  • Collezioni thread-safe: Uso di collezioni speciali del pacchetto java.util.concurrent (ad esempio, ConcurrentHashMap, CopyOnWriteArrayList), progettate per accesso concorrente.

    import java.util.concurrent.ConcurrentHashMap;
    import java.util.Map;
    
    private final Map<String, Integer> safeMap = new ConcurrentHashMap<>();
    
  • Oggetti immutabili (Immutable): Se i dati non cambiano dopo la creazione, possono essere accessibili in modo sicuro da più thread senza sincronizzazione.

  • Variabili locali di thread (ThreadLocal): A ogni thread viene assegnata una copia della variabile, evitando la competizione per l'accesso.

    private final ThreadLocal<Integer> threadId = new ThreadLocal<>();
    
    public void setThreadId() {
        threadId.set(Thread.currentThread().getId());
    }
    
    public int getThreadId() {
        return threadId.get();
    }
    
  • Progettazione architettonica corretta: Minimizzare i dati modificabili condivisi e utilizzare approcci asincroni o trasmissione di messaggi tra thread può ridurre la probabilità di condizioni di gara.