JavaRush /Java Blogu /Random-AZ /Mövzu ilə Java-nı korlaya bilməzsiniz: III hissə - Qarşıl...
Viacheslav
Səviyyə

Mövzu ilə Java-nı korlaya bilməzsiniz: III hissə - Qarşılıqlı əlaqə

Qrupda dərc edilmişdir
Mövzu ilə qarşılıqlı əlaqənin xüsusiyyətlərinin qısa icmalı. Əvvəllər mövzuların bir-biri ilə necə sinxronlaşdığına baxdıq. Bu dəfə mövzuların qarşılıqlı əlaqəsi zamanı yarana biləcək problemlərə nəzər salacağıq və onlardan necə qaçınmaq barədə danışacağıq. Biz həmçinin daha dərindən öyrənmək üçün bəzi faydalı bağlantılar təqdim edəcəyik. Java-nı iplə məhv edə bilməzsiniz: III hissə - qarşılıqlı əlaqə - 1

Giriş

Beləliklə, biz bilirik ki, Java-da " Mövzu Java'yı korlaya bilməz: I Hissə - Mövzular " icmalında oxuya biləcəyiniz mövzular var və mövzular bir-biri ilə sinxronlaşdırıla bilər, biz nəzərdən keçirdik " Mövzu Java-nı korlaya bilməz ” Korlamaq: II hissə - Sinxronizasiya ." Mövzuların bir-biri ilə necə qarşılıqlı əlaqəsi haqqında danışmağın vaxtı gəldi. Onlar ümumi resursları necə bölüşürlər? Bununla bağlı hansı problemlər ola bilər?

Çıxılmaz vəziyyət

Ən pis problem çıxılmaz vəziyyətdir. İki və ya daha çox mövzu bir-birini əbədi gözlədikdə, buna Çıxılmaz Kilit deyilir. Gəlin Oracle veb saytından " Çıxılmaz " konsepsiyasının təsvirindən bir nümunə götürək :
public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s has bowed to me!%n",
                    this.name, bower.getName());
            bower.bowBack(this);
        }
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s has bowed back to me!%n",
                    this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");
        new Thread(() -> alphonse.bow(gaston)).start();
        new Thread(() -> gaston.bow(alphonse)).start();
    }
}
Burada çıxılmaz vəziyyət ilk dəfə görünməyə bilər, lakin proqramınızın icrası ilişibsə, işə başlamağın vaxtıdır jvisualvm: Java-nı iplə məhv edə bilməzsiniz: III hissə - qarşılıqlı əlaqə - 2JVisualVM-də plagin quraşdırılıbsa (Alətlər -> Pluginlər vasitəsilə), biz çıxılmazlığın harada baş verdiyini görə bilərik:
"Thread-1" - Thread t@12
   java.lang.Thread.State: BLOCKED
    at Deadlock$Friend.bowBack(Deadlock.java:16)
    - waiting to lock &lt33a78231> (a Deadlock$Friend) owned by "Thread-0" t@11
Mövzu 1 0-dan kilid gözləyir. Bu niyə baş verir? Thread-1icraya başlayır və metodu icra edir Friend#bow. Açar sözü ilə qeyd olunur synchronized, yəni monitoru ilə götürürük this. Metodun girişində başqa birinə keçid aldıq Friend. İndi ip Thread-1başqa bir metodu yerinə yetirmək istəyir Friendvə bununla da ondan bir kilid əldə edir. Ancaq başqa bir mövzu (bu vəziyyətdə Thread-0) metoda daxil ola bilsə bow, kilid artıq məşğuldur və Thread-1gözləyir Thread-0və əksinə. Bloklama həll edilmir, ona görə də Ölüdür, yəni ölüdür. Həm ölüm tutuşu (buraxmaq mümkün olmayan), həm də qaça bilməyəcəyi ölü blok. Çıxılma mövzusunda videoya baxa bilərsiniz: " Deadlock - Paralellik #1 - Təkmil Java ".

Canlı kilid

Əgər çıxılmaz vəziyyət varsa, deməli Livelock varmı? Bəli, var) Livelock budur ki, iplər zahirən canlı görünür, amma eyni zamanda heç bir şey edə bilmirlər, çünki... onların işlərini davam etdirməyə çalışdıqları şərt yerinə yetirilə bilməz. Əslində, Livelock dalana oxşayır, lakin iplər monitoru gözləyən sistemdə “asılmır”, lakin həmişə nəsə edirlər. Misal üçün:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class App {
    public static final String ANSI_BLUE = "\u001B[34m";
    public static final String ANSI_PURPLE = "\u001B[35m";

    public static void log(String text) {
        String name = Thread.currentThread().getName(); //like Thread-1 or Thread-0
        String color = ANSI_BLUE;
        int val = Integer.valueOf(name.substring(name.lastIndexOf("-") + 1)) + 1;
        if (val != 0) {
            color = ANSI_PURPLE;
        }
        System.out.println(color + name + ": " + text + color);
        try {
            System.out.println(color + name + ": wait for " + val + " sec" + color);
            Thread.currentThread().sleep(val * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Lock first = new ReentrantLock();
        Lock second = new ReentrantLock();

        Runnable locker = () -> {
            boolean firstLocked = false;
            boolean secondLocked = false;
            try {
                while (!firstLocked || !secondLocked) {
                    firstLocked = first.tryLock(100, TimeUnit.MILLISECONDS);
                    log("First Locked: " + firstLocked);
                    secondLocked = second.tryLock(100, TimeUnit.MILLISECONDS);
                    log("Second Locked: " + secondLocked);
                }
                first.unlock();
                second.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        new Thread(locker).start();
        new Thread(locker).start();
    }
}
Bu kodun müvəffəqiyyəti Java mövzu planlayıcısının mövzuları başlatma ardıcıllığından asılıdır. Əvvəlcə başlasa Thead-1, Livelock əldə edəcəyik:
Thread-1: First Locked: true
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
Thread-0: Second Locked: true
Thread-0: wait for 1 sec
Thread-1: Second Locked: false
Thread-1: wait for 2 sec
Thread-0: First Locked: false
Thread-0: wait for 1 sec
...
Nümunədən göründüyü kimi, hər iki ip növbə ilə hər iki kilidi tutmağa çalışır, lakin uğursuz olur. Üstəlik, onlar dalana dirənmirlər, yəni vizual olaraq onlarda hər şey qaydasındadır və öz işlərini görürlər. JVisualVM-ə görə, biz yuxu dövrlərini və park dövrünü görürük (bu, ipin bir kilidi tutmağa çalışdığı zaman, ipin sinxronizasiyasıJava-nı iplə məhv edə bilməzsiniz: III hissə - qarşılıqlı əlaqə - 3 haqqında danışarkən əvvəllər müzakirə etdiyimiz kimi, park vəziyyətinə keçir ). Livelock mövzusunda bir nümunə görə bilərsiniz: " Java - Thread Livelock ".

Aclıq

Bloklamaya əlavə olaraq (çıxmaz və canlı kilid), çox iş parçacığı ilə işləyərkən başqa bir problem var - Aclıq və ya "aclıq". Bu fenomen bloklamadan fərqlidir ki, iplər bloklanmır, lakin onların hər kəs üçün kifayət qədər resursları yoxdur. Buna görə də, bəzi iplər bütün icra müddətini götürsə də, digərləri icra edilə bilməz: Java-nı iplə məhv edə bilməzsiniz: III hissə - qarşılıqlı əlaqə - 4

https://www.logicbig.com/

Super nümunəni burada tapa bilərsiniz: " Java - Thread Starvation and Fairness ". Bu nümunə mövzuların Aclıqda necə işlədiyini və Thread.sleep-dən Thread.wait-ə kiçik bir dəyişikliyin yükü necə bərabər paylaya biləcəyini göstərir. Java-nı iplə məhv edə bilməzsiniz: III hissə - qarşılıqlı əlaqə - 5

Yarış Vəziyyəti

Multithreading ilə işləyərkən "yarış şərti" kimi bir şey var. Bu fenomen, mövzuların müəyyən bir mənbəni öz aralarında paylaşması və kodun bu vəziyyətdə düzgün işləməsini təmin etməyən şəkildə yazılmasından ibarətdir. Bir misala baxaq:
public class App {
    public static int value = 0;

    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                int oldValue = value;
                int newValue = ++value;
                if (oldValue + 1 != newValue) {
                    throw new IllegalStateException(oldValue + " + 1 = " + newValue);
                }
            }
        };
        new Thread(task).start();
        new Thread(task).start();
        new Thread(task).start();
    }
}
Bu kod ilk dəfə xəta yaratmaya bilər. Və bu belə görünə bilər:
Exception in thread "Thread-1" java.lang.IllegalStateException: 7899 + 1 = 7901
    at App.lambda$main$0(App.java:13)
    at java.lang.Thread.run(Thread.java:745)
Gördüyünüz kimi, təyin edilərkən newValuenəsə səhv getdi və newValuedaha çox şey var idi. Yarış vəziyyətindəki bəzi ipliklər valuebu iki komanda arasında dəyişməyi bacardı. Gördüyümüz kimi, iplər arasında bir yarış meydana çıxdı. İndi təsəvvür edin ki, pul əməliyyatlarında oxşar səhvlərə yol verməmək nə qədər vacibdir... Nümunələr və diaqramlar burada da tapıla bilər: “ Java thread-də yarış vəziyyətini simulyasiya etmək üçün kod ”.

Uçucu

Mövzuların qarşılıqlı əlaqəsi haqqında danışarkən, açar sözü xüsusilə qeyd etmək lazımdır volatile. Sadə bir misala baxaq:
public class App {
    public static boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        Runnable whileFlagFalse = () -> {
            while(!flag) {
            }
            System.out.println("Flag is now TRUE");
        };

        new Thread(whileFlagFalse).start();
        Thread.sleep(1000);
        flag = true;
    }
}
Ən maraqlısı odur ki, yüksək ehtimalla işləməyəcək. Yeni mövzu dəyişikliyi görməyəcək flag. Bunu düzəltmək üçün sahə üçün flagaçar söz təyin etməlisiniz volatile. Necə və niyə? Bütün hərəkətlər prosessor tərəfindən həyata keçirilir. Ancaq hesablama nəticələrini bir yerdə saxlamaq lazımdır. Bu məqsədlə prosessorda əsas yaddaş və hardware keşi var. Bu prosessor keşləri verilənlərə əsas yaddaşa daxil olmaqdan daha sürətli daxil olmaq üçün kiçik yaddaş parçası kimidir. Ancaq hər şeyin bir mənfi tərəfi də var: keşdəki məlumatlar cari olmaya bilər (yuxarıdakı nümunədə olduğu kimi, bayraq dəyəri yenilənmədikdə). Beləliklə, açar söz volatileJVM-ə dəyişənimizi keş etmək istəmədiyimizi bildirir. Bu, bütün mövzularda faktiki nəticəni görməyə imkan verir. Bu çox sadələşdirilmiş formuladır. Bu mövzuda " JSR 133 (Java Yaddaş Modeli) FAQvolatile " tərcüməsini oxumaq çox tövsiyə olunur . Həm də sizə “ Java Yaddaş Modeli ” və “ Java Uçucu Açar Sözü ” materialları haqqında ətraflı oxumağı məsləhət görürəm . Bundan əlavə, bunun dəyişikliklərin atomikliyi ilə bağlı deyil, görmə qabiliyyəti ilə bağlı olduğunu xatırlamaq lazımdır . Kodu "Yarış Vəziyyəti"ndən götürsək, IntelliJ Idea-da bir işarə görəcəyik: Bu yoxlama (Yoxlama) 2010-cu ildə Buraxılış Qeydlərində sadalanan IDEA-61117 buraxılışının bir hissəsi kimi IntelliJ Idea-ya əlavə edilmişdir .volatileJava-nı iplə məhv edə bilməzsiniz: III hissə - qarşılıqlı əlaqə - 6

Atomluq

Atom əməliyyatları bölünə bilməyən əməliyyatlardır. Məsələn, dəyişənə dəyər təyin etmək əməliyyatı atomikdir. Təəssüf ki, artım atom əməliyyatı deyil, çünki artım üç əməliyyat tələb edir: köhnə dəyəri əldə edin, ona bir əlavə edin və dəyəri yadda saxlayın. Nə üçün atomiklik vacibdir? Artım nümunəsində, yarış şərti baş verərsə, istənilən vaxt paylaşılan resurs (yəni, paylaşılan dəyər) qəfil dəyişə bilər. Bundan əlavə, 64 bitlik strukturların da atomik olmaması vacibdir, məsələn longdouble. Daha ətraflı burada oxuya bilərsiniz: " 64 bit dəyərləri oxuyarkən və yazarkən atomikliyi təmin edin ". Atomluq problemlərinə misal aşağıdakı nümunədə görünə bilər:
public class App {
    public static int value = 0;
    public static AtomicInteger atomic = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                value++;
                atomic.incrementAndGet();
            }
        };
        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }
        Thread.sleep(300);
        System.out.println(value);
        System.out.println(atomic.get());
    }
}
Atomla işləmək üçün xüsusi sinif Integerbizə həmişə 30000 göstərəcək, lakin valuezaman zaman dəyişəcək. Bu mövzuda " Java-da Atom Dəyişənlərinə Giriş " adlı qısa icmal var . Atomic Müqayisə et və dəyişdir alqoritminə əsaslanır. Bu barədə daha ətraflı Habré-dəki " JDK 7 və 8 nümunəsindən istifadə edərək Kilidsiz alqoritmlərin - CAS və FAA-nın müqayisəsi " məqaləsində və ya Vikipediyada " Mübadilə ilə müqayisə " haqqında məqalədə oxuya bilərsiniz . Java-nı iplə məhv edə bilməzsiniz: III hissə - qarşılıqlı əlaqə - 8

http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html

Əvvəl baş verir

Maraqlı və sirli bir şey var - Əvvəllər olur. Axınlar haqqında danışarkən, bu barədə oxumağa dəyər. Əvvəl baş verir əlaqəsi mövzular arasında hərəkətlərin görünmə sırasını göstərir. Çoxlu təfsir və təfsirlər var. Bu mövzuda ən son hesabatlardan biri bu hesabatdır:
Yəqin ki, bu videonun bu barədə heç nə deməməsi daha yaxşıdır. Ona görə də videonun linkini buraxacağam. Siz " Java - Münasibətlərdən əvvəl baş verənləri anlamaq " kitabını oxuya bilərsiniz .

Nəticələr

Bu baxışda biz iplə qarşılıqlı əlaqənin xüsusiyyətlərinə baxdıq. Yarana biləcək problemləri və onların aşkarlanıb aradan qaldırılması yollarını müzakirə etdik. Mövzu ilə bağlı əlavə materialların siyahısı: #Viaçeslav
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION