1. Livelock ilə tanışlıq
Əgər deadlock — bu, axınların bir‑birini sonsuzadək gözləməsidir, onda livelock (canlı kilidlənmə) — axınlar guya diridir, daim nəsə edir, bir‑birinə yol verirlər, amma... heç kim irəli getmir! Dar dəhlizdə iki nəzakətli insanı təsəvvür edin: “Buyurun, siz keçin!” — “Yox, siz!” — “Yox, siz!” — və belə sonsuzluğa qədər.
Rəsmi tərif
Livelock — axınlar bloklanmır, lakin digər axınların hərəkətlərinə cavab olaraq öz vəziyyətlərini daim dəyişdikləri üçün işi tamamlaya bilmədikləri vəziyyətdir. Onlar “diridir”, aktiv reaksiya verirlər, lakin faydalı iş görülmür.
Bu, praktikada necə görünür?
- Axınlar həmişəlik bloklanmır, amma sonsuz güzəşt döngəsində ilişirlər.
- Sistem asılmır, amma lazımi işi də etmir.
Həyatdan nümunə
- Dar keçiddə iki robot ayrılmalıdır, lakin hər ikisi hər dəfə eyni anda bir‑birinin istiqamətinə addım atır — və yenə mane olurlar.
- İki axın, hər dəfə resursun məşğul olduğunu görüb bir‑birinə yol verir... sonsuzluğa qədər.
2. Java-da livelock nümunəsi
Gəlin kodda livelock modelləşdirək. Sadəlik üçün bir qaşığa ehtiyacı olan iki “işçi” götürək. deadlock-dan fərqli olaraq, qaşıq məşğuldursa, onlar nəzakətlə yol verir və yenidən cəhd edirlər — amma birlikdə, sinxron.
Kod nümunəsi: “Nəzakətli işçilər”
public class LivelockDemo {
static class Spoon {
private Worker owner;
public Spoon(Worker owner) {
this.owner = owner;
}
public Worker getOwner() {
return owner;
}
public synchronized void setOwner(Worker owner) {
this.owner = owner;
}
public synchronized void use() {
// Qaşığın istifadəsi (heç nə etmir)
}
}
static class Worker {
private final String name;
private boolean isHungry = true;
public Worker(String name) {
this.name = name;
}
public String getName() {
return name;
}
public boolean isHungry() {
return isHungry;
}
public void eatWith(Spoon spoon, Worker other) {
while (isHungry) {
// Qaşıq məndə deyilsə — gözləyirəm
if (spoon.getOwner() != this) {
try {
Thread.sleep(1); // Qaşıq azad olana qədər gözləyirik
} catch (InterruptedException ignored) {}
continue;
}
// Digəri acdırsa — qaşığı verirəm
if (other.isHungry()) {
System.out.println(name + ": Qaşığı verirəm " + other.getName());
spoon.setOwner(other);
continue;
}
// Yemək!
System.out.println(name + ": Mən yeyirəm!");
spoon.use();
isHungry = false;
System.out.println(name + ": Mən doydum!");
spoon.setOwner(other);
}
}
}
public static void main(String[] args) {
final Worker alice = new Worker("Alisa");
final Worker bob = new Worker("Bob");
final Spoon spoon = new Spoon(alice);
Thread t1 = new Thread(() -> alice.eatWith(spoon, bob));
Thread t2 = new Thread(() -> bob.eatWith(spoon, alice));
t1.start();
t2.start();
}
}
Nə baş verir?
- Alisa və Bob ikisi də acdır, qaşıq əvvəlcə Alisadadır.
- Alisa görür ki, Bob da acdır və qaşığı ona verir.
- İndi qaşıq Bobdadır, o da görür ki, Alisa acdır və ona verir.
- Qaşıq “işçilər” arasında gəzir, amma heç kim yemir — irəliləyiş yoxdur.
Çıxışda bu necə görünür?
Alisa: Qaşığı Bob-a verirəm
Bob: Qaşığı Alisa-ya verirəm
Alisa: Qaşığı Bob-a verirəm
Bob: Qaşığı Alisa-ya verirəm
...
Livelock-dan necə qurtulmaq olar?
livelock-dan qurtulmaq üçün axınların “nəzakətini” bir qədər seyrəltmək lazımdır. Təkrar cəhddən əvvəl təsadüfi pauza əlavə etmək (məsələn, Thread.sleep vasitəsilə) kömək edir — beləliklə axınlar artıq sinxron reaksiya verməyəcək. Daha “israrlı” strategiya da işləyir: artıq yol vermisənsə, növbəti cəhddən əvvəl daha uzun gözlə. Və alqoritmlərdə həddindən artıq “centlmenlik” etməyin — həddən artıq güzəştlər də ilişmələrə səbəb olur.
3. Starvation (axının aclığı)
Əgər livelock — “əbədi nəzakət”dirsə, starvation (aclıq) — bəzi axınların resursa və ya prosessora ümumiyyətlə çıxış ala bilməməsidir, çünki digərləri daim onları qabaqlayır.
Rəsmi tərif
Starvation — axın digər axınlar onu daim qabaqladığı üçün tələb olunan resursa (CPU, yaddaş, kilid) çıxış ala bilmir. Nəticədə “ac qalan” axın ya çox nadir hallarda icra olunur, ya da ümumiyyətlə icra olunmur.
Starvation-ın səbəbləri
- Ədalətsiz kilidlər. Məsələn, adi synchronized-blok ən çox gözləyənin birinci keçəcəyinə zəmanət vermir.
- Axın prioritetləri. Yüksək prioritetli axınlar prosessoru daim məşğul edirsə, aşağı prioritetlilər “ac qala” bilər (setPriority).
- Digər axınlarda sonsuz döngələr. Kimsə CPU‑nu vermirsə (Thread.sleep və ya Thread.yield() çağırmırsa), digər axınlar icra vaxtı ala bilməyə bilər.
4. Java-da starvation nümunəsi
Nümunə: Aşağı prioritetli axın icra olunmur
public class StarvationDemo {
public static void main(String[] args) {
Runnable highPriorityTask = () -> {
while (true) {
// Yüksək yüklü iş, CPU-nu vermir
}
};
Runnable lowPriorityTask = () -> {
while (true) {
System.out.println("Mən aşağı prioritetli axınam!");
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {}
}
};
Thread high1 = new Thread(highPriorityTask);
Thread high2 = new Thread(highPriorityTask);
Thread low = new Thread(lowPriorityTask);
high1.setPriority(Thread.MAX_PRIORITY); // 10
high2.setPriority(Thread.MAX_PRIORITY); // 10
low.setPriority(Thread.MIN_PRIORITY); // 1
high1.start();
high2.start();
low.start();
}
}
Bu necə təzahür edir?
- Yüksək prioritetli axınlar daim işləyir, CPU‑nu vermirlər.
- Aşağı prioritetli axın demək olar ki, icra olunmur (bəzən ümumiyyətlə icra olunmur).
- Müasir JVM/OS-lərdə prioritetlər planlaşdırıcı tərəfindən yumşaldıla bilər, amma bəzi sistemlərdə aclıq nəzərə çarpır.
Bir başqa nümunə: ədalətsiz kilidə görə starvation
public class StarvationLockDemo {
private static final Object lock = new Object();
public static void main(String[] args) {
// lock-u daim ələ keçirən 5 axın
for (int i = 0; i < 5; i++) {
new Thread(() -> {
while (true) {
synchronized (lock) {
// lock-u uzun müddət tuturuq
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {}
}
}
}).start();
}
// Bir aclıq çəkən axın
new Thread(() -> {
while (true) {
synchronized (lock) {
System.out.println("Aclıq çəkən axın lock-u aldı!");
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {}
}
}
}).start();
}
}
Bu nümunədə “ac qalan” axın digər axınlar kilidi daim tutursa, lock-a çox uzun müddət çıxış ala bilməyə bilər.
5. Livelock və starvation-ı necə aşkar etmək və qarşısını almaq
Necə aşkar etmək olar?
- Livelock: proqram işləyir, axınlar asılmayıb, amma irəliləyiş yoxdur (nəticə yoxdur, döngülərdən çıxış yoxdur).
- Starvation: bəzi axınlar demək olar ki, icra olunmur (loqlarda mesajlar nadirdir və ya ümumiyyətlə yoxdur).
Alətlər
- Loqlaşdırma: işin başlanğıcını/sonunu, resursun tutulması/buraxılmasını işarələyin.
- Monitorinq: VisualVM, Java Mission Control — hansı axınların aktiv olduğunu və nə ilə məşğul olduqlarını izləyin.
- Thread dump: axınların lock gözləməsində ilişmədiyini yoxlayın.
Necə qarşısını almaq olar?
Livelock üçün:
- Həddən artıq “nəzakətli” güzəştlər etməyin — təkrar cəhddən əvvəl kiçik təsadüfi gecikmə əlavə edin (Thread.sleep).
- Axınların sinxron davranışından qaçmaq üçün təkrar cəhdlərin ardıcıllığına təsadüfilik daxil edin.
- Bloklanmayan strukturlardan/alqoritmlərdən istifadə edin (atomik dəyişənlər, CAS yanaşması).
Starvation üçün:
- “Ədalətli” kilidlərdən istifadə edin. Məsələn, ReentrantLock fairness ilə:
java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock(true); // ədalətli rejim
- Axın prioritetlərindən sui‑istifadə etməyin — çox vaxt standart prioriteti saxlayın.
- Kritik bölmələrdə vaxtı minimuma endirin (synchronized/Lock).
- Xidmət göstərilməsi FIFO-ya yaxın olan tapşırıq növbələrindən istifadə edin.
Cədvəl: Deadlock, Livelock, Starvation — müqayisə
| Problem | Nə baş verir | Axınlar “aktivdir”? | İrəliləyiş? | Tipik simptom |
|---|---|---|---|---|
| Deadlock | Hamı bir-birini gözləyir | Yox | Yox | Proqram “donub” |
| Livelock | Hamı yol verir, amma irəliləmir | Bəli | Yox | Axınlar işləyir, nəticə yoxdur |
| Starvation | Bəziləri işləyir, digərləri demək olar ki, yox | Bəli (bir qismi) | Qismən | Bəzi axınlar “ac qalır” |
Bənzətmələr və maraqlı faktlar
- Livelock — ayrılmaq üçün hər ikisi eyni anda sola addım atan iki nəfər kimidir və yenə toqquşurlar.
- Starvation — kassirin yalnız “öz” müştərilərinə xidmət etdiyi, digərlərinin isə sonsuza qədər gözlədiyi növbə kimidir.
Maraqlı fakt: livelock deadlock-dan daha nadirdir, lakin onu aşkar etmək daha çətindir — proqram “asmır”, nəsə edir!
6. Livelock və starvation ilə işləyərkən tipik səhvlər
Səhv №1: “Nəzakətli güzəştlər” gecikməsiz. Axınlar pauzasız olaraq həddən artıq tez‑tez bir‑birinə yol verirsə, livelock-a düşə bilərlər. Resursu yenidən tutmağa cəhddən əvvəl kiçik təsadüfi gecikmə əlavə edin (Thread.sleep).
Səhv №2: Yalnız synchronized-da gözləmək, ədalətli kilidlərsiz. Axınların sayı çox olduqda, adi synchronized “ən ac”ın çıxış alacağına zəmanət vermir. Tənzimləyici baxımdan kritikdirsə, fairness ilə ReentrantLock istifadə edin.
Səhv №3: Axın prioritetlərindən sui‑istifadə. Vacib axınları setPriority vasitəsilə “sürətləndirmək” çox vaxt digər axınlar üçün starvation-a gətirir. Həqiqi zərurət olmadıqda prioritetlərə toxunmayın.
Səhv №4: Monitorinq və loqlaşdırmanın olmaması. Livelock və starvation loqlar olmadan çətin görünür: proqram “işləyir”, amma nəticə yoxdur. Açar hadisələri loqlaşdırın və profaylerlərdən/axın damp-larından istifadə edin.
Səhv №5: Həddən artıq uzun kritik bölmələr. Axın lock-u uzun müddət saxlayırsa, qalanlar gözləyəcək (və ya “ac qalacaq”). synchronized/Lock-bloklarında vaxtı minimuma endirin.
GO TO FULL VERSION