CodeGym /Kurslar /JAVA 25 SELF /Ümumi resurslar və sinxronizasiya: giriş problemləri

Ümumi resurslar və sinxronizasiya: giriş problemləri

JAVA 25 SELF
Səviyyə, Dərs
Mövcuddur

1. Ümumi resurs nədir

Ümumi resurslarla artıq rastlaşmısınız. Ailənin yaşadığı ev — həmin ailənin ümumi resursudur. Ofis soyuducusu — bütün həmkarlar üçün ümumi resursdur. Düşünürəm, ideya aydındır.

Proqramlaşdırmada ümumi resurs — bir neçə axının eyni vaxtda müraciət edə bildiyi dəyişən, obyekt və ya verilənlər strukturudur. Bu, belə ola bilər:

  • Emal olunmuş sifarişlərin sayını göstərən sayğac dəyişəni.
  • Bəzi axınlar tərəfindən doldurulan, digərləri tərəfindən emal olunan müraciətlər siyahısı.
  • Bir neçə axının yazdığı açıq fayl.
  • Proqramın müxtəlif hissələrinin işlədiyi verilənlər bazasına qoşulma.

Java-da bir neçə axının müraciət edə biləcəyi istənilən obyekt və ya dəyişən potensial “ümumi resurs”a çevrilir.

Ümumi resurs nümunəsi: qlobal sayğac

public class Counter {
    public int value = 0;
}

Əgər bir neçə axın bu sayğacı artıracaqsa, onlar eyni value dəyişəninə müraciət edəcəklər — budur sizə ümumi resurs.

2. Eyni vaxtda giriş problemləri

Təkaxınlı proqramda hər şey sadədir: bir axın — bir icraçı — o, kodun üstündən relslə gedən qatar kimi keçir. Amma işə bir neçə axın qoşulan kimi həqiqi “qılınc rəqsi” başlayır: axınlar ən gözlənilməz yerlərdə bir-birinin işinə qarışa bilər.

Race condition (yarış vəziyyəti)

Race condition — proqramın nəticəsinin axınların hərəkətlərinin necə “qarışmasına” bağlı olduğu vəziyyətdir. Yəni eyni proqramı bir neçə dəfə işə salsanız, nəticə fərqli ola bilər — bu, baq deyil, çoxaxınlılığın “xüsusiyyəti”dir.

Klassik nümunə: iki axın sayğacı artırır

Gəlin sadə bir vəziyyəti modelləşdirək: ümumi sayğacımız var və iki axın onun dəyərini min dəfə artırır.

public class Counter {
    public int value = 0;
}

public class CounterDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Runnable incrementTask = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.value++; // TƏHLÜKƏLİ YER!
            }
        };

        Thread t1 = new Thread(incrementTask);
        Thread t2 = new Thread(incrementTask);

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("Gözlənilən dəyər: 2000");
        System.out.println("Faktiki dəyər: " + counter.value);
    }
}

Ekranda nə görəcəyik?

Bəzən — 2000, bəzən — 1985, bəzən isə — 1937... Niyə? Çünki counter.value++ əməliyyatı atomik deyil! O, üç addımdan ibarətdir:

  1. Hazırkı counter.value dəyərini oxumaq.
  2. Onu 1 vahid artırmaq.
  3. Geriyə yazmaq.

Əgər iki axın eyni vaxtda eyni dəyəri oxuyub hər ikisi onu artırsalar və sonra nəticəni ayrı-ayrılıqda yazsalar — nəticədə artırmaların biri “itirilə” bilər. Bu, lost update — yeniləmənin itirilməsi deməkdir.

Obyektin qeyri-konsistent vəziyyəti

Əgər bir neçə sahədən ibarət mürəkkəb obyektiniz varsa və axınlar eyni vaxtda müxtəlif sahələri dəyişirsə, obyekt “ qəribə” və ya qeyri-konsistent vəziyyətə düşə bilər. Məsələn, hesabdakı məbləğ azalıb, amma əməliyyatların tarixi yenilənməyib — müştəri təşvişdə, mühasib şokda.

3. Sinxronizasiya niyə lazımdır

Sinxronizasiya — proqramda deməyin yoludur: “Dayan, bu kod hissəsini eyni anda yalnız bir axın icra etməlidir! Qalanlar — gözləsin!” Bu, tualetin qapısındakı “Təmizlik! Daxil olmayın!” lövhəsi kimidir: bir nəfər içəridə olanadək, başqaları çöldə gözləyir (və uzun qalanı ürəklərində danlayırlar).

Məlumatların bütövlüyünə zəmanət

Əgər sayğacımızın həmişə düzgün artırılmasını istəyiriksə, onun dəyərinin bir neçə axın tərəfindən eyni vaxtda dəyişdirilməsini qadağan etməliyik.

Nümunə: sayğacın artırılmasının sinxronizasiyası

public class Counter {
    public int value = 0;

    public synchronized void increment() {
        value++;
    }
}

İndi əgər iki axın increment() çağırsa, müəyyən anda yalnız biri bu metodu icra edə biləcək. İkincisi birincisi bitirənə qədər gözləyəcək.

Sxem: sinxronizasiya zamanı nə baş verir

+-------------------+
|  Axın 1           |    --\
+-------------------+      \
      |                      \
      V                       \
+-------------------+          > [ synchronized increment() ]
|  Axın 2           |    --/  /
+-------------------+      / /
      |                    / /
      V                   / /
+-------------------+    / /
|  Axın 3           | --/ /
+-------------------+    /
      |                 /
      V                /
+-------------------+ /
|  Axın N           |/
+-------------------+

Bütün axınlar qorunan kod hissəsini icra etmək üçün növbəyə dayanır. Eyni vaxtda “kritik bölmə”də (synchronized-blokda) yalnız bir axın ola bilər.

4. Sinxronizasiya üsullarına qısa giriş

Java-da sinxronizasiya tək bir üsul deyil, ümumi resursu eyni vaxtda girişdən qorumağa imkan verən bütöv bir “arsenal”dır.

Açar söz synchronized

Bu, Java-da əsas sinxronizasiya alətidir. Onu iki yolla istifadə etmək olar:

Sinxronlaşdırılmış metod

public synchronized void increment() {
    value++;
}

Sinxronlaşdırılmış blok

public void increment() {
    synchronized (this) {
        value++;
    }
}

Burada this — kilidləmənin baş verdiyi obyektdir. Bir axın bu bloku icra etdiyi müddətdə, eyni obyektlə eyni bloka daxil olmaq istəyən digər axınlar gözləyəcəklər.

java.util.concurrent-dən ixtisaslaşdırılmış siniflər

  • Lock, ReentrantLock — synchronized üçün daha çevik alternativ.
  • ReadWriteLock — oxuma və yazma kilidlərinin ayrılması üçün.
  • Semaphore — kodu eyni vaxtda icra edən axınların sayını məhdudlaşdırır.
  • CountDownLatch, CyclicBarrier və digərləri — axınların işini koordinasiya etmək üçün.

Vacib: Bu gün biz yalnız əsaslarla tanış oluruq — bu siniflər barədə bir az sonra danışacağıq.

5. Praktik nümunə: çoxaxınlı sayğaclı tətbiq

Tutaq ki, hansısa xidmətə istifadəçi müraciətlərinin statistikasını yazırıq. Hər axın — ümumi sayğacı artıran ayrı bir istifadəçidir.

Sinxronizasiya olmadan

public class Counter {
    public int value = 0;
}

public class MultiThreadCounterDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Runnable user = () -> {
            for (int i = 0; i < 10000; i++) {
                counter.value++;
            }
        };

        Thread t1 = new Thread(user);
        Thread t2 = new Thread(user);
        Thread t3 = new Thread(user);

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();

        System.out.println("Gözlənilən dəyər: 30000");
        System.out.println("Faktiki dəyər: " + counter.value);
    }
}

Nəticə: Demək olar ki, həmişə 30000-dən az. Bəzən — çox daha az! Niyə? Çünki axınlar bir-birini “üstələyir”.

Sinxronizasiya: səhvi düzəldirik

public class Counter {
    public int value = 0;

    public synchronized void increment() {
        value++;
    }
}

public class MultiThreadCounterDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        Runnable user = () -> {
            for (int i = 0; i < 10000; i++) {
                counter.increment();
            }
        };

        Thread t1 = new Thread(user);
        Thread t2 = new Thread(user);
        Thread t3 = new Thread(user);

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();

        System.out.println("Gözlənilən dəyər: 30000");
        System.out.println("Faktiki dəyər: " + counter.value);
    }
}

Nəticə: Həmişə 30000. Hurra, sinxronizasiya işləyir!

6. Faydalı incəliklər

Vizualizasiya: race condition necə görünür

Gəlin kiçik bir cədvəl çəkək ki, iki axının necə “artırmanı itirə” biləcəyini göstərək:

Addım Axın 1 Axın 2 value dəyəri
1 value=0 oxuyur 0
2 value=0 oxuyur 0
3 1-ə qədər artırır 0
4 1-ə qədər artırır 0
5 1 yazır 1
6 1 yazır 1

Sinxronizasiya nə zaman lazımdır

Sinxronizasiya həmişə lazım deyil. Dəyişən öz kiçik dünyasında yaşayır və onunla yalnız bir axın işləyirsə — rahat ola bilərsiniz. Amma onu başqa axınlarla paylaşan kimi, sinxronizasiyasız mümkün deyil. Hətta hər şeyin yaxşı olacağını düşünsəniz belə — inanmayın. Yarış səhvi məkrlidir: uzun müddət gizlənə bilər, sonra isə ən uyğunsuz anda qəfil üzə çıxar.

Gələcək üçün baza: başqa hansı sinxronizasiya vasitələri var

Bu gün yalnız əsas alətlə — synchronized — tanış olduq. Növbəti mühazirələrdə baxacağıq:

  • Obyekt monitoru necə işləyir və hansı kilid növləri var.
  • Statik sinxronlaşdırılmış metodlar nədir (static + synchronized).
  • volatile açar sözü necə işləyir və nə üçün lazımdır.
  • Sinxronizasiya üçün müasir siniflər hansılardır (Lock, Semaphore və s.).

7. Ümumi resurslarla işləyərkən tipik səhvlər

Səhv № 1: Çoxaxınlılığı nəzərə almamaq.
Ən çox yayılan səhvlərdən biri — dəyişənə bir neçə axının giriş edə biləcəyini nəzərə almamaqdır. Hətta proqram hazırda təkaxınlı olsa belə, sonradan kimsə axınlar əlavə edəcək — və baqlar “heç yerdən” peyda olacaq.

Səhv № 2: Yetərsiz və ya həddən artıq sinxronizasiya.
Əgər ümumi resursa girişi sinxronlaşdırmasanız — race condition və qeyri-konsistent məlumatlar əldə edəcəksiniz. Əgər hər şeyi ard-arda sinxronlaşdırsanız, proqram kilidlərdən “boğulacaq” və yavaşlayacaq. Həmişə yalnız həqiqətən lazım olanı sinxronlaşdırmağa çalışın.

Səhv № 3: Yanlış obyekt üzərində sinxronizasiya.
Əgər girişi müxtəlif obyektlər üzərində sinxronlaşdırsanız (məsələn, lokal dəyişənlər və ya sətir literalları üzərində), bu, ümumi resursu qorumayacaq. Bütün axınlar eyni obyekt üzərində sinxronlaşdırılmalıdır.

Səhv № 4: Atomik olmayan əməliyyatlardan atomiklik gözləmək.
i++ əməliyyatı atomik deyil! Hətta dəyişən volatile kimi elan edilsə belə, bu, inkrementi atomik etmir. Belə əməliyyatlar üçün sinxronizasiya lazımdır.

Səhv № 5: “Mənim şansım gətirdi, məndə hər şey işləyir”.
Race condition sizin kompüterinizdə üzə çıxmaya bilər, amma mütləq serverdə və ya istifadəçidə özünü göstərəcək. Heç vaxt çoxaxınlı proqramlarda “bəxtə” arxayın olmayın!

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
İstehsal xəttinin ayıklanması 🏭
İstehsal xəttinin ayıklanması 🏭
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Komanda oyununda xallar 🎮
Komanda oyununda xallar 🎮
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION