Sobes.tech
Middle

Vorbește despre condiția de cursă (race condition) și cum poate fi evitată în aplicațiile multi-thread.

sobes.tech AI

Răspuns de la AI

Starea de cursă este un defect de proiectare într-un sistem multi-thread, în care rezultatul execuției programului depinde de ordinea în care diferitele fire de execuție execută părți ale codului. Acest lucru se întâmplă atunci când mai multe fire de execuție accesează simultan o resursă partajată modificabilă fără sincronizare adecvată.

Pentru a evita condițiile de cursă în Java, se utilizează următoarele abordări:

  1. Utilizarea metodelor și blocurilor sincronizate:

    class Counter {
        private int count = 0;
    
        // Metodă sincronizată
        public synchronized void increment() {
            count++;
        }
    
        // Bloc sincronizat
        public void decrement() {
            synchronized (this) {
                count--;
            }
        }
    }
    

    Cuvântul cheie synchronized garantează că doar un fir de execuție poate executa cod sincronizat pentru acest obiect (sau pentru clasă dacă metoda este statică) la un moment dat.

  2. Utilizarea clasei Lock din pachetul java.util.concurrent.locks:

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class SafeCounter {
        private int count = 0;
        private final Lock lock = new ReentrantLock();
    
        public void increment() {
            lock.lock(); // Obținerea blocajului
            try {
                count++;
            } finally {
                lock.unlock(); // Eliberarea blocajului
            }
        }
    }
    

    Lock oferă mecanisme de blocare mai flexibile comparativ cu synchronized.

  3. Utilizarea variabilelor atomice din pachetul java.util.concurrent.atomic:

    import java.util.concurrent.atomic.AtomicInteger;
    
    class AtomicCounter {
        private AtomicInteger count = new AtomicInteger(0);
    
        public void increment() {
            count.incrementAndGet(); // Operație atomică
        }
    }
    

    Variabilele atomice oferă operații atomice (de exemplu, incrementare, decrementare) care se execută ca o singură unitate și nu pot fi întrerupte de alt fir de execuție.

  4. Utilizarea colecțiilor sigure pentru fire de execuție din pachetul java.util.concurrent: De exemplu, ConcurrentHashMap, CopyOnWriteArrayList. Aceste colecții sunt proiectate pentru utilizare sigură în medii multi-thread.

  5. Evitarea stării partajate modificabile: Dacă este posibil, datele accesibile mai multor fire de execuție trebuie făcute imuabile (immutable) sau separate pentru fiecare fir.

  6. Utilizarea volatile: Cuvântul cheie volatile garantează vizibilitatea modificărilor variabilei pentru toate firele de execuție, dar nu asigură atomitatea operațiilor (cu excepția citirii și scrierii tipurilor primitive, cu excepția long și double, și referințe). Este util în combinație cu alte mecanisme sau pentru operații simple, independente de stare.

    class VolatileFlag {
        volatile boolean flag = false; // Garanție de vizibilitate
    
        public void setFlag() {
            flag = true;
        }
    
        public boolean isFlag() {
            return flag;
        }
    }
    

Alegerea metodei potrivite depinde de situația specifică și de cerințele de performanță și complexitate ale codului.