Sobes.tech
Middle

Czym jest blok synchronized w Java i jakie problemy mogą wystąpić podczas jego używania?

sobes.tech AI

Odpowiedź od AI

blok synchronized w Javie jest używany do zapewnienia synchronizacji dostępu do wspólnego zasobu między wieloma wątkami. Gwarantuje to, że tylko jeden wątek może wykonywać kod wewnątrz bloku w danym momencie.

Struktura bloku synchronized:

synchronized (obiekt_monitor) {
    // Sekcja krytyczna kodu
    // Operacje na wspólnym zasobie
}
  • obiekt_monitor: dowolny obiekt, który działa jako monitor (lock). Wątki próbujące wejść do bloku synchronized z tym samym obiektem monitora będą blokowane do momentu opuszczenia bloku przez bieżący wątek.

Problemy, które mogą wystąpić przy użyciu synchronized:

  • Deadlock (zakleszczenie): Dwa lub więcej wątków oczekuje na zwolnienie zasobów, które są zajęte przez inne wątki, co prowadzi do zatrzymania programu. Może się to zdarzyć, jeśli wątki próbują uzyskać blokady w różnych kolejnościach.
  • Obniżenie wydajności: Synchronizacja blokuje wykonanie innych wątków oczekujących na dostęp do monitora, co może spowolnić działanie aplikacji równoległych.
  • Nie w każdym scenariuszu: synchronized zapewnia wyłączny dostęp. W niektórych przypadkach może być potrzebne bardziej elastyczne zarządzanie dostępem (np. odczyt wielu wątków jednocześnie, zapis w jednym wątku).
  • Złożoność debugowania: Problemy z synchronizacją mogą być nieoczywiste i trudne do odtworzenia i debugowania.

Przykład zakleszczenia:

class Resource {
    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void method1(Resource other) {
        synchronized (lock1) {
            try {
                Thread.sleep(100); // Symulacja pracy
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " uzyskał lock1");
            synchronized (other.lock2) {
                System.out.println(Thread.currentThread().getName() + " uzyskał lock2 innego zasobu");
            }
        }
    }

    public void method2(Resource other) {
        synchronized (lock2) {
             try {
                Thread.sleep(100); // Symulacja pracy
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " uzyskał lock2");
            synchronized (other.lock1) {
                System.out.println(Thread.currentThread().getName() + " uzyskał lock1 innego zasobu");
            }
        }
    }
}

public class DeadlockExample {
    public static void main(String[] args) {
        Resource r1 = new Resource();
        Resource r2 = new Resource();

        new Thread(() -> r1.method1(r2), "Wątek-1").start();
        new Thread(() -> r2.method2(r1), "Wątek-2").start();
    }
}

W tym przykładzie, Wątek-1 próbuje uzyskać lock1 na r1, a następnie lock2 na r2. Wątek-2 próbuje uzyskać lock2 na r2, a następnie lock1 na r1. Jeśli oba uzyskają swoje pierwsze blokady jednocześnie, dojdzie do zakleszczenia.