JavaRush /Java blogi /Random-UZ /Siz Java-ni mavzu bilan buzolmaysiz: III qism - O'zaro ta...
Viacheslav
Daraja

Siz Java-ni mavzu bilan buzolmaysiz: III qism - O'zaro ta'sir

Guruhda nashr etilgan
Ip o'zaro ta'sirining xususiyatlari haqida qisqacha ma'lumot. Ilgari biz iplar bir-biri bilan qanday sinxronlashayotganini ko'rib chiqdik. Bu safar biz iplar o'zaro ta'sirlashganda paydo bo'lishi mumkin bo'lgan muammolarni ko'rib chiqamiz va ularni qanday qilib oldini olish mumkinligi haqida gapiramiz. Shuningdek, biz chuqurroq o'rganish uchun foydali havolalarni taqdim etamiz. Siz Java-ni ip bilan buzolmaysiz: III qism - o'zaro ta'sir - 1

Kirish

Shunday qilib, biz bilamizki, Java-da mavzular mavjud bo'lib, ular haqida " Javani buzmaydi: I qism - mavzular " sharhida o'qishingiz mumkin va mavzular bir-biri bilan sinxronlashtirilishi mumkin, biz ushbu sharhda ko'rib chiqdik " Mavzu Java-ni buzmaydi ” Buzilishi: II qism - Sinxronizatsiya . Iplarning bir-biri bilan o'zaro ta'siri haqida gapirish vaqti keldi. Qanday qilib ular umumiy resurslarni baham ko'rishadi? Bu bilan qanday muammolar bo'lishi mumkin?

O'lik qulf

Eng yomon muammo - bu o'lik qulf. Ikki yoki undan ortiq iplar bir-birini abadiy kutsa, bu o'lik blok deb ataladi. Keling, Oracle veb-saytidan " Deadlock " tushunchasining tavsifidan misol keltiraylik :
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();
    }
}
Bu yerda boshlanish birinchi marta paydo bo'lmasligi mumkin, lekin agar dasturingiz ishlamay qolsa, ishga tushirish vaqti keldi jvisualvm: Siz Java-ni ip bilan buzolmaysiz: III qism - o'zaro ta'sir - 2Agar JVisualVM-da plagin o'rnatilgan bo'lsa (Asboblar -> Plaginlar orqali), biz blokirovkaning qayerda sodir bo'lganini ko'rishimiz mumkin:
"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
1-mavzu 0-mavzudan qulfni kutmoqda. Nima uchun bu sodir bo'ladi? Thread-1bajarishni boshlaydi va usulni bajaradi Friend#bow. U kalit so'z bilan belgilanadi synchronized, ya'ni biz monitorni tomonidan tanlaymiz this. Usulga kirishda biz boshqasiga havola oldik Friend. Endi ip Thread-1boshqa usulda usulni bajarishni xohlaydi Friendva shu bilan undan ham qulf oladi. Ammo agar boshqa ip (bu holda Thread-0) usulga kirishga muvaffaq bo'lsa bow, u holda qulf allaqachon band va Thread-1kutish Thread-0, va aksincha. Bloklash hal etilmaydi, shuning uchun u O'lik, ya'ni o'lik. Ham o'lim tutqichi (uni bo'shatib bo'lmaydigan), ham qochib qutula olmaydigan o'lik blok. Tugallanish mavzusida siz videoni tomosha qilishingiz mumkin: " Deadlock - Concurrency №1 - Advanced Java ".

Jonli qulf

Agar o'lik qulf bo'lsa, unda Livelock bormi? Ha, bor) Livelock - bu iplar tashqi ko'rinishida jonli ko'rinadi, lekin ayni paytda ular hech narsa qila olmaydi, chunki ... ular o'z ishini davom ettirishga harakat qilayotgan shartni bajarish mumkin emas. Aslini olganda, Livelock o'lik qulfga o'xshaydi, lekin iplar monitorni kutayotgan tizimda "osib qo'ymaydi", lekin har doim nimadir qiladi. Masalan:
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();
    }
}
Ushbu kodning muvaffaqiyati Java ish tartibini rejalashtiruvchisi iplarni ishga tushirish tartibiga bog'liq. Agar u birinchi bo'lib boshlansa Thead-1, biz Livelockni olamiz:
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
...
Misoldan ko'rinib turibdiki, ikkala ip navbatma-navbat ikkala qulfni ham ushlashga harakat qiladi, ammo ular muvaffaqiyatsizlikka uchraydi. Bundan tashqari, ular boshi berk ko'chada emas, ya'ni vizual ravishda ular bilan hamma narsa yaxshi va ular o'z ishlarini qilmoqdalar. Siz Java-ni ip bilan buzolmaysiz: III qism - o'zaro ta'sir - 3JVisualVM ma'lumotlariga ko'ra, biz uyqu davrlarini va park davrini ko'ramiz (bu ip qulfni egallashga harakat qilganda, u bog' holatiga o'tadi, biz avvalroq mavzuni sinxronlashtirish haqida gapirganda muhokama qildik ). Livelock mavzusida siz misolni ko'rishingiz mumkin: " Java - Thread Livelock ".

Ochlik

Bloklashdan tashqari (o'yin blokirovkasi va jonli qulf) ko'p oqim bilan ishlashda yana bir muammo mavjud - ochlik yoki "ochlik". Bu hodisa blokirovkadan farq qiladi, chunki iplar bloklanmagan, ammo ular hamma uchun etarli resurslarga ega emas. Shuning uchun, ba'zi bir ish zarrachalari barcha bajarilish vaqtini egallagan bo'lsa, boshqalari bajarilmaydi: Siz Java-ni ip bilan buzolmaysiz: III qism - o'zaro ta'sir - 4

https://www.logicbig.com/

Super misolni bu erda topish mumkin: " Java - Thread Starvation and Fairness ". Bu misol ochlik rejimida iplar qanday ishlashini va Thread.sleep-dan Thread.wait-ga bir kichik o'zgartirish yukni qanday teng taqsimlashini ko'rsatadi. Siz Java-ni ip bilan buzolmaysiz: III qism - o'zaro ta'sir - 5

Poyga holati

Multithreading bilan ishlashda "poyga sharti" kabi narsa mavjud. Bu hodisa shundan iboratki, iplar ma'lum bir manbani o'zaro taqsimlaydi va kod bu holda to'g'ri ishlashni ta'minlamaydigan tarzda yozilgan. Keling, bir misolni ko'rib chiqaylik:
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 birinchi marta xatolikka olib kelmasligi mumkin. Va bu shunday ko'rinishi mumkin:
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)
Ko'rib turganingizdek, u tayinlanayotganda, newValuenimadir noto'g'ri ketdi va newValueyana ko'p narsalar bor edi. Poyga holatidagi ba'zi mavzular valuebu ikki jamoa o'rtasida o'zgarishiga muvaffaq bo'ldi. Ko'rib turganimizdek, iplar o'rtasida poyga paydo bo'ldi. Endi tasavvur qiling-a, pul o'tkazmalari bilan o'xshash xatolarga yo'l qo'ymaslik qanchalik muhimligini tasavvur qiling... Misollar va diagrammalarni bu erda ham topish mumkin: “ Java threadida poyga holatini simulyatsiya qilish uchun kod ”.

O'zgaruvchan

Iplarning o'zaro ta'siri haqida gapirganda, ayniqsa, kalit so'zni ta'kidlash kerak volatile. Keling, oddiy misolni ko'rib chiqaylik:
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;
    }
}
Eng qizig'i shundaki, yuqori ehtimollik bilan u ishlamaydi. Yangi mavzu o'zgarishlarni ko'rmaydi flag. Buni tuzatish uchun flagsiz maydon uchun kalit so'zni ko'rsatishingiz kerak volatile. Qanday qilib va ​​nima uchun? Barcha harakatlar protsessor tomonidan amalga oshiriladi. Lekin hisoblash natijalarini biror joyda saqlash kerak. Buning uchun protsessorda asosiy xotira va apparat keshi mavjud. Ushbu protsessor keshlari asosiy xotiradan ko'ra tezroq ma'lumotlarga kirish uchun xotiraning kichik qismiga o'xshaydi. Lekin har bir narsaning salbiy tomoni ham bor: keshdagi ma'lumotlar joriy bo'lmasligi mumkin (yuqoridagi misolda, bayroq qiymati yangilanmaganida). Shunday qilib, kalit so'z volatileJVMga o'zgaruvchimizni keshlashni xohlamasligimizni aytadi. Bu sizga barcha mavzulardagi haqiqiy natijani ko'rish imkonini beradi. Bu juda soddalashtirilgan formuladir. Ushbu mavzu bo'yicha " JSR 133 (Java Memory Model) FAQvolatile " tarjimasini o'qish tavsiya etiladi . Shuningdek, sizga " Java xotira modeli " va " Java uchuvchan kalit so'zi " materiallari haqida ko'proq o'qishni maslahat beraman . Bundan tashqari, bu o'zgarishlarning atomikligi haqida emas, balki ko'rinish haqida ekanligini unutmaslik kerak . Agar kodni "Poyga holati" dan olsak, biz IntelliJ Idea-da maslahatni ko'ramiz: Ushbu tekshiruv (tekshiruv) IntelliJ Idea-ga IDEA-61117 sonining bir qismi sifatida qo'shilgan bo'lib , u 2010 yilda Relizlar eslatmalarida keltirilgan .volatileSiz Java-ni ip bilan buzolmaysiz: III qism - o'zaro ta'sir - 6

Atomlik

Atom operatsiyalari - bu bo'linib bo'lmaydigan operatsiyalar. Masalan, o'zgaruvchiga qiymat berish operatsiyasi atomikdir. Afsuski, o'sish atom operatsiyasi emas, chunki o'sish uchta operatsiyani talab qiladi: eski qiymatni oling, unga bittasini qo'shing va qiymatni saqlang. Nima uchun atomiklik muhim? O'sish misolida, agar poyga holati yuzaga kelsa, har qanday vaqtda umumiy manba (ya'ni, umumiy qiymat) to'satdan o'zgarishi mumkin. Bunga qo'shimcha ravishda, 64-bitli tuzilmalar ham atomik emasligi muhim, masalan longva double. Batafsil bu yerda o'qishingiz mumkin: " 64 bitli qiymatlarni o'qish va yozishda atomiklikni ta'minlang ". Atom bilan bog'liq muammolarga misolni quyidagi misolda ko'rish mumkin:
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());
    }
}
Atom bilan ishlash uchun maxsus sinf Integerbizga har doim 30000 ni ko'rsatadi, lekin valuevaqti-vaqti bilan o'zgaradi. Ushbu mavzu bo'yicha qisqacha sharh mavjud " Javada atom o'zgaruvchilariga kirish ". Atom solishtirish va almashtirish algoritmiga asoslangan. Bu haqda Habré-dagi " JDK 7 va 8 misolidan foydalangan holda blokirovkasiz algoritmlarni taqqoslash - CAS va FAA " maqolasida yoki Vikipediyadagi " Almashinuv bilan taqqoslash " haqidagi maqolada o'qishingiz mumkin . Siz Java-ni ip bilan buzolmaysiz: III qism - o'zaro ta'sir - 8

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

Oldin sodir bo'ladi

Qiziqarli va sirli narsa bor - Oldin sodir bo'ladi. Oqimlar haqida gapirganda, bu haqda o'qishga arziydi. Oldin sodir bo'ladigan munosabat iplar orasidagi harakatlarning ko'rish tartibini bildiradi. Ko'p talqin va talqinlar mavjud. Ushbu mavzu bo'yicha eng so'nggi hisobotlardan biri bu hisobot:
Bu videoda bu haqda hech narsa aytilmagani yaxshiroqdir. Shuning uchun men videoga havolani qoldiraman. " Java - munosabatlardan oldin sodir bo'lishini tushunish " ni o'qishingiz mumkin .

Natijalar

Ushbu sharhda biz iplarning o'zaro ta'sirining xususiyatlarini ko'rib chiqdik. Biz yuzaga kelishi mumkin bo'lgan muammolar va ularni aniqlash va bartaraf etish yo'llarini muhokama qildik. Mavzu bo'yicha qo'shimcha materiallar ro'yxati: #Viacheslav
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION